diff options
author | Ben Murdoch <benm@google.com> | 2011-05-05 14:36:32 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2011-05-10 15:38:30 +0100 |
commit | f05b935882198ccf7d81675736e3aeb089c5113a (patch) | |
tree | 4ea0ca838d9ef1b15cf17ddb3928efb427c7e5a1 /WebKitTools/Scripts/webkitpy/common | |
parent | 60fbdcc62bced8db2cb1fd233cc4d1e4ea17db1b (diff) | |
download | external_webkit-f05b935882198ccf7d81675736e3aeb089c5113a.zip external_webkit-f05b935882198ccf7d81675736e3aeb089c5113a.tar.gz external_webkit-f05b935882198ccf7d81675736e3aeb089c5113a.tar.bz2 |
Merge WebKit at r74534: Initial merge by git.
Change-Id: I6ccd1154fa1b19c2ec2a66878eb675738735f1eb
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/common')
82 files changed, 0 insertions, 11868 deletions
diff --git a/WebKitTools/Scripts/webkitpy/common/__init__.py b/WebKitTools/Scripts/webkitpy/common/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/common/array_stream.py b/WebKitTools/Scripts/webkitpy/common/array_stream.py deleted file mode 100644 index e425d02..0000000 --- a/WebKitTools/Scripts/webkitpy/common/array_stream.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Package that private an array-based implementation of a stream.""" - - -class ArrayStream(object): - """Simple class that implmements a stream interface on top of an array. - - This is used primarily by unit test classes to mock output streams. It - performs a similar function to StringIO, but (a) it is write-only, and - (b) it can be used to retrieve each individual write(); StringIO - concatenates all of the writes together. - """ - - def __init__(self): - self._contents = [] - - def write(self, msg): - """Implement stream.write() by appending to the stream's contents.""" - self._contents.append(msg) - - def get(self): - """Return the contents of a stream (as an array).""" - return self._contents - - def reset(self): - """Empty the stream.""" - self._contents = [] - - def empty(self): - """Return whether the stream is empty.""" - return (len(self._contents) == 0) - - def flush(self): - """Flush the stream (a no-op implemented for compatibility).""" - pass - - def __repr__(self): - return '<ArrayStream: ' + str(self._contents) + '>' diff --git a/WebKitTools/Scripts/webkitpy/common/array_stream_unittest.py b/WebKitTools/Scripts/webkitpy/common/array_stream_unittest.py deleted file mode 100644 index 1a9b34a..0000000 --- a/WebKitTools/Scripts/webkitpy/common/array_stream_unittest.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Unit tests for array_stream.py.""" - -import pdb -import unittest - -from webkitpy.common.array_stream import ArrayStream - - -class ArrayStreamTest(unittest.TestCase): - def assertEmpty(self, a_stream): - self.assertTrue(a_stream.empty()) - - def assertNotEmpty(self, a_stream): - self.assertFalse(a_stream.empty()) - - def assertContentsMatch(self, a_stream, contents): - self.assertEquals(a_stream.get(), contents) - - def test_basics(self): - a = ArrayStream() - self.assertEmpty(a) - self.assertContentsMatch(a, []) - - a.flush() - self.assertEmpty(a) - self.assertContentsMatch(a, []) - - a.write("foo") - a.write("bar") - self.assertNotEmpty(a) - self.assertContentsMatch(a, ["foo", "bar"]) - - a.flush() - self.assertNotEmpty(a) - self.assertContentsMatch(a, ["foo", "bar"]) - - a.reset() - self.assertEmpty(a) - self.assertContentsMatch(a, []) - - self.assertEquals(str(a), "<ArrayStream: []>") - - a.write("foo") - self.assertNotEmpty(a) - self.assertContentsMatch(a, ["foo"]) - self.assertEquals(str(a), "<ArrayStream: ['foo']>") - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/__init__.py b/WebKitTools/Scripts/webkitpy/common/checkout/__init__.py deleted file mode 100644 index 597dcbd..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Required for Python to search this directory for module files - -from api import Checkout diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/api.py b/WebKitTools/Scripts/webkitpy/common/checkout/api.py deleted file mode 100644 index dbe1e84..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/api.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import os -import StringIO - -from webkitpy.common.checkout.changelog import ChangeLog -from webkitpy.common.checkout.commitinfo import CommitInfo -from webkitpy.common.checkout.scm import CommitMessage -from webkitpy.common.memoized import memoized -from webkitpy.common.net.bugzilla import parse_bug_id -from webkitpy.common.system.executive import Executive, run_command, ScriptError -from webkitpy.common.system.deprecated_logging import log - - -# This class represents the WebKit-specific parts of the checkout (like -# ChangeLogs). -# FIXME: Move a bunch of ChangeLog-specific processing from SCM to this object. -class Checkout(object): - def __init__(self, scm): - self._scm = scm - - def _is_path_to_changelog(self, path): - return os.path.basename(path) == "ChangeLog" - - def _latest_entry_for_changelog_at_revision(self, changelog_path, revision): - changelog_contents = self._scm.contents_at_revision(changelog_path, revision) - # contents_at_revision returns a byte array (str()), but we know - # that ChangeLog files are utf-8. parse_latest_entry_from_file - # expects a file-like object which vends unicode(), so we decode here. - changelog_file = StringIO.StringIO(changelog_contents.decode("utf-8")) - return ChangeLog.parse_latest_entry_from_file(changelog_file) - - def changelog_entries_for_revision(self, revision): - changed_files = self._scm.changed_files_for_revision(revision) - return [self._latest_entry_for_changelog_at_revision(path, revision) for path in changed_files if self._is_path_to_changelog(path)] - - @memoized - def commit_info_for_revision(self, revision): - committer_email = self._scm.committer_email_for_revision(revision) - changelog_entries = self.changelog_entries_for_revision(revision) - # Assume for now that the first entry has everything we need: - # FIXME: This will throw an exception if there were no ChangeLogs. - if not len(changelog_entries): - return None - changelog_entry = changelog_entries[0] - changelog_data = { - "bug_id": parse_bug_id(changelog_entry.contents()), - "author_name": changelog_entry.author_name(), - "author_email": changelog_entry.author_email(), - "author": changelog_entry.author(), - "reviewer_text": changelog_entry.reviewer_text(), - "reviewer": changelog_entry.reviewer(), - } - # We could pass the changelog_entry instead of a dictionary here, but that makes - # mocking slightly more involved, and would make aggregating data from multiple - # entries more difficult to wire in if we need to do that in the future. - return CommitInfo(revision, committer_email, changelog_data) - - def bug_id_for_revision(self, revision): - return self.commit_info_for_revision(revision).bug_id() - - def _modified_files_matching_predicate(self, git_commit, predicate, changed_files=None): - # SCM returns paths relative to scm.checkout_root - # Callers (especially those using the ChangeLog class) may - # expect absolute paths, so this method returns absolute paths. - if not changed_files: - changed_files = self._scm.changed_files(git_commit) - absolute_paths = [os.path.join(self._scm.checkout_root, path) for path in changed_files] - return [path for path in absolute_paths if predicate(path)] - - def modified_changelogs(self, git_commit, changed_files=None): - return self._modified_files_matching_predicate(git_commit, self._is_path_to_changelog, changed_files=changed_files) - - def modified_non_changelogs(self, git_commit, changed_files=None): - return self._modified_files_matching_predicate(git_commit, lambda path: not self._is_path_to_changelog(path), changed_files=changed_files) - - def commit_message_for_this_commit(self, git_commit, changed_files=None): - changelog_paths = self.modified_changelogs(git_commit, changed_files) - if not len(changelog_paths): - raise ScriptError(message="Found no modified ChangeLogs, cannot create a commit message.\n" - "All changes require a ChangeLog. See:\n" - "http://webkit.org/coding/contributing.html") - - changelog_messages = [] - for changelog_path in changelog_paths: - log("Parsing ChangeLog: %s" % changelog_path) - changelog_entry = ChangeLog(changelog_path).latest_entry() - if not changelog_entry: - raise ScriptError(message="Failed to parse ChangeLog: %s" % os.path.abspath(changelog_path)) - changelog_messages.append(changelog_entry.contents()) - - # FIXME: We should sort and label the ChangeLog messages like commit-log-editor does. - return CommitMessage("".join(changelog_messages).splitlines()) - - def recent_commit_infos_for_files(self, paths): - revisions = set(sum(map(self._scm.revisions_changing_file, paths), [])) - return set(map(self.commit_info_for_revision, revisions)) - - def suggested_reviewers(self, git_commit, changed_files=None): - changed_files = self.modified_non_changelogs(git_commit, changed_files) - commit_infos = self.recent_commit_infos_for_files(changed_files) - reviewers = [commit_info.reviewer() for commit_info in commit_infos if commit_info.reviewer()] - reviewers.extend([commit_info.author() for commit_info in commit_infos if commit_info.author() and commit_info.author().can_review]) - return sorted(set(reviewers)) - - def bug_id_for_this_commit(self, git_commit, changed_files=None): - try: - return parse_bug_id(self.commit_message_for_this_commit(git_commit, changed_files).message()) - except ScriptError, e: - pass # We might not have ChangeLogs. - - def apply_patch(self, patch, force=False): - # It's possible that the patch was not made from the root directory. - # We should detect and handle that case. - # FIXME: Move _scm.script_path here once we get rid of all the dependencies. - args = [self._scm.script_path('svn-apply')] - if patch.reviewer(): - args += ['--reviewer', patch.reviewer().full_name] - if force: - args.append('--force') - run_command(args, input=patch.contents()) - - def apply_reverse_diff(self, revision): - self._scm.apply_reverse_diff(revision) - - # We revert the ChangeLogs because removing lines from a ChangeLog - # doesn't make sense. ChangeLogs are append only. - changelog_paths = self.modified_changelogs(git_commit=None) - if len(changelog_paths): - self._scm.revert_files(changelog_paths) - - conflicts = self._scm.conflicted_files() - if len(conflicts): - raise ScriptError(message="Failed to apply reverse diff for revision %s because of the following conflicts:\n%s" % (revision, "\n".join(conflicts))) diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py deleted file mode 100644 index 1f97abd..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/api_unittest.py +++ /dev/null @@ -1,196 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from __future__ import with_statement - -import codecs -import os -import shutil -import tempfile -import unittest - -from webkitpy.common.checkout.api import Checkout -from webkitpy.common.checkout.changelog import ChangeLogEntry -from webkitpy.common.checkout.scm import detect_scm_system, CommitMessage -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.thirdparty.mock import Mock - - -# FIXME: Copied from scm_unittest.py -def write_into_file_at_path(file_path, contents, encoding="utf-8"): - with codecs.open(file_path, "w", encoding) as file: - file.write(contents) - - -_changelog1entry1 = u"""2010-03-25 Tor Arne Vestb\u00f8 <vestbo@webkit.org> - - Unreviewed build fix to un-break webkit-patch land. - - Move commit_message_for_this_commit from scm to checkout - https://bugs.webkit.org/show_bug.cgi?id=36629 - - * Scripts/webkitpy/common/checkout/api.py: import scm.CommitMessage -""" -_changelog1entry2 = u"""2010-03-25 Adam Barth <abarth@webkit.org> - - Reviewed by Eric Seidel. - - Move commit_message_for_this_commit from scm to checkout - https://bugs.webkit.org/show_bug.cgi?id=36629 - - * Scripts/webkitpy/common/checkout/api.py: -""" -_changelog1 = u"\n".join([_changelog1entry1, _changelog1entry2]) -_changelog2 = u"""2010-03-25 Tor Arne Vestb\u00f8 <vestbo@webkit.org> - - Unreviewed build fix to un-break webkit-patch land. - - Second part of this complicated change. - - * Path/To/Complicated/File: Added. - -2010-03-25 Adam Barth <abarth@webkit.org> - - Reviewed by Eric Seidel. - - Filler change. -""" - -class CommitMessageForThisCommitTest(unittest.TestCase): - expected_commit_message = u"""2010-03-25 Tor Arne Vestb\u00f8 <vestbo@webkit.org> - - Unreviewed build fix to un-break webkit-patch land. - - Move commit_message_for_this_commit from scm to checkout - https://bugs.webkit.org/show_bug.cgi?id=36629 - - * Scripts/webkitpy/common/checkout/api.py: import scm.CommitMessage -2010-03-25 Tor Arne Vestb\u00f8 <vestbo@webkit.org> - - Unreviewed build fix to un-break webkit-patch land. - - Second part of this complicated change. - - * Path/To/Complicated/File: Added. -""" - - def setUp(self): - self.temp_dir = tempfile.mkdtemp(suffix="changelogs") - self.old_cwd = os.getcwd() - os.chdir(self.temp_dir) - write_into_file_at_path("ChangeLog1", _changelog1) - write_into_file_at_path("ChangeLog2", _changelog2) - - def tearDown(self): - shutil.rmtree(self.temp_dir, ignore_errors=True) - os.chdir(self.old_cwd) - - # FIXME: This should not need to touch the file system, however - # ChangeLog is difficult to mock at current. - def test_commit_message_for_this_commit(self): - checkout = Checkout(None) - checkout.modified_changelogs = lambda git_commit, changed_files=None: ["ChangeLog1", "ChangeLog2"] - output = OutputCapture() - expected_stderr = "Parsing ChangeLog: ChangeLog1\nParsing ChangeLog: ChangeLog2\n" - commit_message = output.assert_outputs(self, checkout.commit_message_for_this_commit, - kwargs={"git_commit": None}, expected_stderr=expected_stderr) - self.assertEqual(commit_message.message(), self.expected_commit_message) - - -class CheckoutTest(unittest.TestCase): - def test_latest_entry_for_changelog_at_revision(self): - scm = Mock() - def mock_contents_at_revision(changelog_path, revision): - self.assertEqual(changelog_path, "foo") - self.assertEqual(revision, "bar") - # contents_at_revision is expected to return a byte array (str) - # so we encode our unicode ChangeLog down to a utf-8 stream. - return _changelog1.encode("utf-8") - scm.contents_at_revision = mock_contents_at_revision - checkout = Checkout(scm) - entry = checkout._latest_entry_for_changelog_at_revision("foo", "bar") - self.assertEqual(entry.contents(), _changelog1entry1) - - def test_commit_info_for_revision(self): - scm = Mock() - scm.committer_email_for_revision = lambda revision: "committer@example.com" - checkout = Checkout(scm) - checkout.changelog_entries_for_revision = lambda revision: [ChangeLogEntry(_changelog1entry1)] - commitinfo = checkout.commit_info_for_revision(4) - self.assertEqual(commitinfo.bug_id(), 36629) - self.assertEqual(commitinfo.author_name(), u"Tor Arne Vestb\u00f8") - self.assertEqual(commitinfo.author_email(), "vestbo@webkit.org") - self.assertEqual(commitinfo.reviewer_text(), None) - self.assertEqual(commitinfo.reviewer(), None) - self.assertEqual(commitinfo.committer_email(), "committer@example.com") - self.assertEqual(commitinfo.committer(), None) - - checkout.changelog_entries_for_revision = lambda revision: [] - self.assertEqual(checkout.commit_info_for_revision(1), None) - - def test_bug_id_for_revision(self): - scm = Mock() - scm.committer_email_for_revision = lambda revision: "committer@example.com" - checkout = Checkout(scm) - checkout.changelog_entries_for_revision = lambda revision: [ChangeLogEntry(_changelog1entry1)] - self.assertEqual(checkout.bug_id_for_revision(4), 36629) - - def test_bug_id_for_this_commit(self): - scm = Mock() - checkout = Checkout(scm) - checkout.commit_message_for_this_commit = lambda git_commit, changed_files=None: CommitMessage(ChangeLogEntry(_changelog1entry1).contents().splitlines()) - self.assertEqual(checkout.bug_id_for_this_commit(git_commit=None), 36629) - - def test_modified_changelogs(self): - scm = Mock() - scm.checkout_root = "/foo/bar" - scm.changed_files = lambda git_commit: ["file1", "ChangeLog", "relative/path/ChangeLog"] - checkout = Checkout(scm) - expected_changlogs = ["/foo/bar/ChangeLog", "/foo/bar/relative/path/ChangeLog"] - self.assertEqual(checkout.modified_changelogs(git_commit=None), expected_changlogs) - - def test_suggested_reviewers(self): - def mock_changelog_entries_for_revision(revision): - if revision % 2 == 0: - return [ChangeLogEntry(_changelog1entry1)] - return [ChangeLogEntry(_changelog1entry2)] - - def mock_revisions_changing_file(path, limit=5): - if path.endswith("ChangeLog"): - return [3] - return [4, 8] - - scm = Mock() - scm.checkout_root = "/foo/bar" - scm.changed_files = lambda git_commit: ["file1", "file2", "relative/path/ChangeLog"] - scm.revisions_changing_file = mock_revisions_changing_file - checkout = Checkout(scm) - checkout.changelog_entries_for_revision = mock_changelog_entries_for_revision - reviewers = checkout.suggested_reviewers(git_commit=None) - reviewer_names = [reviewer.full_name for reviewer in reviewers] - self.assertEqual(reviewer_names, [u'Tor Arne Vestb\xf8']) diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/changelog.py b/WebKitTools/Scripts/webkitpy/common/checkout/changelog.py deleted file mode 100644 index 40657eb..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/changelog.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright (C) 2009, Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# WebKit's Python module for parsing and modifying ChangeLog files - -import codecs -import fileinput # inplace file editing for set_reviewer_in_changelog -import os.path -import re -import textwrap - -from webkitpy.common.system.deprecated_logging import log -from webkitpy.common.config.committers import CommitterList -from webkitpy.common.net.bugzilla import parse_bug_id - - -def view_source_url(revision_number): - # FIMXE: This doesn't really belong in this file, but we don't have a - # better home for it yet. - # Maybe eventually a webkit_config.py? - return "http://trac.webkit.org/changeset/%s" % revision_number - - -class ChangeLogEntry(object): - # e.g. 2009-06-03 Eric Seidel <eric@webkit.org> - date_line_regexp = r'^(?P<date>\d{4}-\d{2}-\d{2})\s+(?P<name>.+?)\s+<(?P<email>[^<>]+)>$' - - def __init__(self, contents, committer_list=CommitterList()): - self._contents = contents - self._committer_list = committer_list - self._parse_entry() - - def _parse_entry(self): - match = re.match(self.date_line_regexp, self._contents, re.MULTILINE) - if not match: - log("WARNING: Creating invalid ChangeLogEntry:\n%s" % self._contents) - - # FIXME: group("name") does not seem to be Unicode? Probably due to self._contents not being unicode. - self._author_name = match.group("name") if match else None - self._author_email = match.group("email") if match else None - - match = re.search("^\s+Reviewed by (?P<reviewer>.*?)[\.,]?\s*$", self._contents, re.MULTILINE) # Discard everything after the first period - self._reviewer_text = match.group("reviewer") if match else None - - self._reviewer = self._committer_list.committer_by_name(self._reviewer_text) - self._author = self._committer_list.committer_by_email(self._author_email) or self._committer_list.committer_by_name(self._author_name) - - def author_name(self): - return self._author_name - - def author_email(self): - return self._author_email - - def author(self): - return self._author # Might be None - - # FIXME: Eventually we would like to map reviwer names to reviewer objects. - # See https://bugs.webkit.org/show_bug.cgi?id=26533 - def reviewer_text(self): - return self._reviewer_text - - def reviewer(self): - return self._reviewer # Might be None - - def contents(self): - return self._contents - - def bug_id(self): - return parse_bug_id(self._contents) - - -# FIXME: Various methods on ChangeLog should move into ChangeLogEntry instead. -class ChangeLog(object): - - def __init__(self, path): - self.path = path - - _changelog_indent = " " * 8 - - @staticmethod - def parse_latest_entry_from_file(changelog_file): - """changelog_file must be a file-like object which returns - unicode strings. Use codecs.open or StringIO(unicode()) - to pass file objects to this class.""" - date_line_regexp = re.compile(ChangeLogEntry.date_line_regexp) - entry_lines = [] - # The first line should be a date line. - first_line = changelog_file.readline() - assert(isinstance(first_line, unicode)) - if not date_line_regexp.match(first_line): - return None - entry_lines.append(first_line) - - for line in changelog_file: - # If we've hit the next entry, return. - if date_line_regexp.match(line): - # Remove the extra newline at the end - return ChangeLogEntry(''.join(entry_lines[:-1])) - entry_lines.append(line) - return None # We never found a date line! - - def latest_entry(self): - # ChangeLog files are always UTF-8, we read them in as such to support Reviewers with unicode in their names. - changelog_file = codecs.open(self.path, "r", "utf-8") - try: - return self.parse_latest_entry_from_file(changelog_file) - finally: - changelog_file.close() - - # _wrap_line and _wrap_lines exist to work around - # http://bugs.python.org/issue1859 - - def _wrap_line(self, line): - return textwrap.fill(line, - width=70, - initial_indent=self._changelog_indent, - # Don't break urls which may be longer than width. - break_long_words=False, - subsequent_indent=self._changelog_indent) - - # Workaround as suggested by guido in - # http://bugs.python.org/issue1859#msg60040 - - def _wrap_lines(self, message): - lines = [self._wrap_line(line) for line in message.splitlines()] - return "\n".join(lines) - - # This probably does not belong in changelogs.py - def _message_for_revert(self, revision, reason, bug_url): - message = "Unreviewed, rolling out r%s.\n" % revision - message += "%s\n" % view_source_url(revision) - if bug_url: - message += "%s\n" % bug_url - # Add an extra new line after the rollout links, before any reason. - message += "\n" - if reason: - message += "%s\n\n" % reason - return self._wrap_lines(message) - - def update_for_revert(self, revision, reason, bug_url=None): - reviewed_by_regexp = re.compile( - "%sReviewed by NOBODY \(OOPS!\)\." % self._changelog_indent) - removing_boilerplate = False - # inplace=1 creates a backup file and re-directs stdout to the file - for line in fileinput.FileInput(self.path, inplace=1): - if reviewed_by_regexp.search(line): - message_lines = self._message_for_revert(revision, - reason, - bug_url) - print reviewed_by_regexp.sub(message_lines, line), - # Remove all the ChangeLog boilerplate between the Reviewed by - # line and the first changed file. - removing_boilerplate = True - elif removing_boilerplate: - if line.find('*') >= 0: # each changed file is preceded by a * - removing_boilerplate = False - - if not removing_boilerplate: - print line, - - def set_reviewer(self, reviewer): - # inplace=1 creates a backup file and re-directs stdout to the file - for line in fileinput.FileInput(self.path, inplace=1): - # Trailing comma suppresses printing newline - print line.replace("NOBODY (OOPS!)", reviewer.encode("utf-8")), - - def set_short_description_and_bug_url(self, short_description, bug_url): - message = "%s\n %s" % (short_description, bug_url) - for line in fileinput.FileInput(self.path, inplace=1): - print line.replace("Need a short description and bug URL (OOPS!)", message.encode("utf-8")), diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py deleted file mode 100644 index 6aeb1f8..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py +++ /dev/null @@ -1,203 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from __future__ import with_statement - -import codecs -import os -import tempfile -import unittest - -from StringIO import StringIO - -from webkitpy.common.checkout.changelog import * - - -class ChangeLogTest(unittest.TestCase): - - _example_entry = u'''2009-08-17 Peter Kasting <pkasting@google.com> - - Reviewed by Tor Arne Vestb\xf8. - - https://bugs.webkit.org/show_bug.cgi?id=27323 - Only add Cygwin to the path when it isn't already there. This avoids - causing problems for people who purposefully have non-Cygwin versions of - executables like svn in front of the Cygwin ones in their paths. - - * DumpRenderTree/win/DumpRenderTree.vcproj: - * DumpRenderTree/win/ImageDiff.vcproj: - * DumpRenderTree/win/TestNetscapePlugin/TestNetscapePlugin.vcproj: -''' - - # More example text than we need. Eventually we need to support parsing this all and write tests for the parsing. - _example_changelog = u"""2009-08-17 Tor Arne Vestb\xf8 <vestbo@webkit.org> - - <http://webkit.org/b/28393> check-webkit-style: add check for use of std::max()/std::min() instead of MAX()/MIN() - - Reviewed by David Levin. - - * Scripts/modules/cpp_style.py: - (_ERROR_CATEGORIES): Added 'runtime/max_min_macros'. - (check_max_min_macros): Added. Returns level 4 error when MAX() - and MIN() macros are used in header files and C++ source files. - (check_style): Added call to check_max_min_macros(). - * Scripts/modules/cpp_style_unittest.py: Added unit tests. - (test_max_macro): Added. - (test_min_macro): Added. - -2009-08-16 David Kilzer <ddkilzer@apple.com> - - Backed out r47343 which was mistakenly committed - - * Scripts/bugzilla-tool: - * Scripts/modules/scm.py: - -2009-06-18 Darin Adler <darin@apple.com> - - Rubber stamped by Mark Rowe. - - * DumpRenderTree/mac/DumpRenderTreeWindow.mm: - (-[DumpRenderTreeWindow close]): Resolved crashes seen during regression - tests. The close method can be called on a window that's already closed - so we can't assert here. - -== Rolled over to ChangeLog-2009-06-16 == -""" - - def test_latest_entry_parse(self): - changelog_contents = u"%s\n%s" % (self._example_entry, self._example_changelog) - changelog_file = StringIO(changelog_contents) - latest_entry = ChangeLog.parse_latest_entry_from_file(changelog_file) - self.assertEquals(latest_entry.contents(), self._example_entry) - self.assertEquals(latest_entry.author_name(), "Peter Kasting") - self.assertEquals(latest_entry.author_email(), "pkasting@google.com") - self.assertEquals(latest_entry.reviewer_text(), u"Tor Arne Vestb\xf8") - self.assertTrue(latest_entry.reviewer()) # Make sure that our UTF8-based lookup of Tor works. - - @staticmethod - def _write_tmp_file_with_contents(byte_array): - assert(isinstance(byte_array, str)) - (file_descriptor, file_path) = tempfile.mkstemp() # NamedTemporaryFile always deletes the file on close in python < 2.6 - with os.fdopen(file_descriptor, "w") as file: - file.write(byte_array) - return file_path - - @staticmethod - def _read_file_contents(file_path, encoding): - with codecs.open(file_path, "r", encoding) as file: - return file.read() - - _new_entry_boilerplate = '''2009-08-19 Eric Seidel <eric@webkit.org> - - Reviewed by NOBODY (OOPS!). - - Need a short description and bug URL (OOPS!) - - * Scripts/bugzilla-tool: -''' - - def test_set_reviewer(self): - changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog) - changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8")) - reviewer_name = 'Test Reviewer' - ChangeLog(changelog_path).set_reviewer(reviewer_name) - actual_contents = self._read_file_contents(changelog_path, "utf-8") - expected_contents = changelog_contents.replace('NOBODY (OOPS!)', reviewer_name) - os.remove(changelog_path) - self.assertEquals(actual_contents, expected_contents) - - def test_set_short_description_and_bug_url(self): - changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog) - changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8")) - short_description = "A short description" - bug_url = "http://example.com/b/2344" - ChangeLog(changelog_path).set_short_description_and_bug_url(short_description, bug_url) - actual_contents = self._read_file_contents(changelog_path, "utf-8") - expected_message = "%s\n %s" % (short_description, bug_url) - expected_contents = changelog_contents.replace("Need a short description and bug URL (OOPS!)", expected_message) - os.remove(changelog_path) - self.assertEquals(actual_contents, expected_contents) - - _revert_message = """ Unreviewed, rolling out r12345. - http://trac.webkit.org/changeset/12345 - http://example.com/123 - - This is a very long reason which should be long enough so that - _message_for_revert will need to wrap it. We'll also include - a - https://veryveryveryveryverylongbugurl.com/reallylongbugthingy.cgi?bug_id=12354 - link so that we can make sure we wrap that right too. -""" - - def test_message_for_revert(self): - changelog = ChangeLog("/fake/path") - long_reason = "This is a very long reason which should be long enough so that _message_for_revert will need to wrap it. We'll also include a https://veryveryveryveryverylongbugurl.com/reallylongbugthingy.cgi?bug_id=12354 link so that we can make sure we wrap that right too." - message = changelog._message_for_revert(12345, long_reason, "http://example.com/123") - self.assertEquals(message, self._revert_message) - - _revert_entry_with_bug_url = '''2009-08-19 Eric Seidel <eric@webkit.org> - - Unreviewed, rolling out r12345. - http://trac.webkit.org/changeset/12345 - http://example.com/123 - - Reason - - * Scripts/bugzilla-tool: -''' - - _revert_entry_without_bug_url = '''2009-08-19 Eric Seidel <eric@webkit.org> - - Unreviewed, rolling out r12345. - http://trac.webkit.org/changeset/12345 - - Reason - - * Scripts/bugzilla-tool: -''' - - def _assert_update_for_revert_output(self, args, expected_entry): - changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog) - changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8")) - changelog = ChangeLog(changelog_path) - changelog.update_for_revert(*args) - actual_entry = changelog.latest_entry() - os.remove(changelog_path) - self.assertEquals(actual_entry.contents(), expected_entry) - self.assertEquals(actual_entry.reviewer_text(), None) - # These checks could be removed to allow this to work on other entries: - self.assertEquals(actual_entry.author_name(), "Eric Seidel") - self.assertEquals(actual_entry.author_email(), "eric@webkit.org") - - def test_update_for_revert(self): - self._assert_update_for_revert_output([12345, "Reason"], self._revert_entry_without_bug_url) - self._assert_update_for_revert_output([12345, "Reason", "http://example.com/123"], self._revert_entry_with_bug_url) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo.py b/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo.py deleted file mode 100644 index 448d530..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# WebKit's python module for holding information on a commit - -from webkitpy.common.checkout.changelog import view_source_url -from webkitpy.common.config.committers import CommitterList - - -class CommitInfo(object): - def __init__(self, revision, committer_email, changelog_data, committer_list=CommitterList()): - self._revision = revision - self._committer_email = committer_email - self._bug_id = changelog_data["bug_id"] - self._author_name = changelog_data["author_name"] - self._author_email = changelog_data["author_email"] - self._author = changelog_data["author"] - self._reviewer_text = changelog_data["reviewer_text"] - self._reviewer = changelog_data["reviewer"] - - # Derived values: - self._committer = committer_list.committer_by_email(committer_email) - - def revision(self): - return self._revision - - def committer(self): - return self._committer # None if committer isn't in committers.py - - def committer_email(self): - return self._committer_email - - def bug_id(self): - return self._bug_id # May be None - - def author(self): - return self._author # May be None - - def author_name(self): - return self._author_name - - def author_email(self): - return self._author_email - - def reviewer(self): - return self._reviewer # May be None - - def reviewer_text(self): - return self._reviewer_text # May be None - - def responsible_parties(self): - responsible_parties = [ - self.committer(), - self.author(), - self.reviewer(), - ] - return set([party for party in responsible_parties if party]) # Filter out None - - # FIXME: It is slightly lame that this "view" method is on this "model" class (in MVC terms) - def blame_string(self, bugs): - string = "r%s:\n" % self.revision() - string += " %s\n" % view_source_url(self.revision()) - string += " Bug: %s (%s)\n" % (self.bug_id(), bugs.bug_url_for_bug_id(self.bug_id())) - author_line = "\"%s\" <%s>" % (self.author_name(), self.author_email()) - string += " Author: %s\n" % (self.author() or author_line) - string += " Reviewer: %s\n" % (self.reviewer() or self.reviewer_text()) - string += " Committer: %s" % self.committer() - return string diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo_unittest.py deleted file mode 100644 index f58e6f1..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/commitinfo_unittest.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.checkout.commitinfo import CommitInfo -from webkitpy.common.config.committers import CommitterList, Committer, Reviewer - -class CommitInfoTest(unittest.TestCase): - - def test_commit_info_creation(self): - author = Committer("Author", "author@example.com") - committer = Committer("Committer", "committer@example.com") - reviewer = Reviewer("Reviewer", "reviewer@example.com") - committer_list = CommitterList(committers=[author, committer], reviewers=[reviewer]) - - changelog_data = { - "bug_id": 1234, - "author_name": "Committer", - "author_email": "author@example.com", - "author": author, - "reviewer_text": "Reviewer", - "reviewer": reviewer, - } - commit = CommitInfo(123, "committer@example.com", changelog_data, committer_list) - - self.assertEqual(commit.revision(), 123) - self.assertEqual(commit.bug_id(), 1234) - self.assertEqual(commit.author_name(), "Committer") - self.assertEqual(commit.author_email(), "author@example.com") - self.assertEqual(commit.author(), author) - self.assertEqual(commit.reviewer_text(), "Reviewer") - self.assertEqual(commit.reviewer(), reviewer) - self.assertEqual(commit.committer(), committer) - self.assertEqual(commit.committer_email(), "committer@example.com") - self.assertEqual(commit.responsible_parties(), set([author, committer, reviewer])) diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser.py b/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser.py deleted file mode 100644 index a6ea756..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""WebKit's Python module for interacting with patches.""" - -import logging -import re - -_log = logging.getLogger("webkitpy.common.checkout.diff_parser") - - -# FIXME: This is broken. We should compile our regexps up-front -# instead of using a custom cache. -_regexp_compile_cache = {} - - -# FIXME: This function should be removed. -def match(pattern, string): - """Matches the string with the pattern, caching the compiled regexp.""" - if not pattern in _regexp_compile_cache: - _regexp_compile_cache[pattern] = re.compile(pattern) - return _regexp_compile_cache[pattern].match(string) - - -# FIXME: This belongs on DiffParser (e.g. as to_svn_diff()). -def git_diff_to_svn_diff(line): - """Converts a git formatted diff line to a svn formatted line. - - Args: - line: A string representing a line of the diff. - """ - # FIXME: This list should be a class member on DiffParser. - # These regexp patterns should be compiled once instead of every time. - conversion_patterns = (("^diff --git \w/(.+) \w/(?P<FilePath>.+)", lambda matched: "Index: " + matched.group('FilePath') + "\n"), - ("^new file.*", lambda matched: "\n"), - ("^index [0-9a-f]{7}\.\.[0-9a-f]{7} [0-9]{6}", lambda matched: "===================================================================\n"), - ("^--- \w/(?P<FilePath>.+)", lambda matched: "--- " + matched.group('FilePath') + "\n"), - ("^\+\+\+ \w/(?P<FilePath>.+)", lambda matched: "+++ " + matched.group('FilePath') + "\n")) - - for pattern, conversion in conversion_patterns: - matched = match(pattern, line) - if matched: - return conversion(matched) - return line - - -# FIXME: This method belongs on DiffParser -def get_diff_converter(first_diff_line): - """Gets a converter function of diff lines. - - Args: - first_diff_line: The first filename line of a diff file. - If this line is git formatted, we'll return a - converter from git to SVN. - """ - if match(r"^diff --git \w/", first_diff_line): - return git_diff_to_svn_diff - return lambda input: input - - -_INITIAL_STATE = 1 -_DECLARED_FILE_PATH = 2 -_PROCESSING_CHUNK = 3 - - -class DiffFile(object): - """Contains the information for one file in a patch. - - The field "lines" is a list which contains tuples in this format: - (deleted_line_number, new_line_number, line_string) - If deleted_line_number is zero, it means this line is newly added. - If new_line_number is zero, it means this line is deleted. - """ - # FIXME: Tuples generally grow into classes. We should consider - # adding a DiffLine object. - - def added_or_modified_line_numbers(self): - # This logic was moved from patchreader.py, but may not be - # the right API for this object long-term. - return [line[1] for line in self.lines if not line[0]] - - def __init__(self, filename): - self.filename = filename - self.lines = [] - - def add_new_line(self, line_number, line): - self.lines.append((0, line_number, line)) - - def add_deleted_line(self, line_number, line): - self.lines.append((line_number, 0, line)) - - def add_unchanged_line(self, deleted_line_number, new_line_number, line): - self.lines.append((deleted_line_number, new_line_number, line)) - - -class DiffParser(object): - """A parser for a patch file. - - The field "files" is a dict whose key is the filename and value is - a DiffFile object. - """ - - # FIXME: This function is way too long and needs to be broken up. - def __init__(self, diff_input): - """Parses a diff. - - Args: - diff_input: An iterable object. - """ - state = _INITIAL_STATE - - self.files = {} - current_file = None - old_diff_line = None - new_diff_line = None - for line in diff_input: - line = line.rstrip("\n") - if state == _INITIAL_STATE: - transform_line = get_diff_converter(line) - line = transform_line(line) - - file_declaration = match(r"^Index: (?P<FilePath>.+)", line) - if file_declaration: - filename = file_declaration.group('FilePath') - current_file = DiffFile(filename) - self.files[filename] = current_file - state = _DECLARED_FILE_PATH - continue - - lines_changed = match(r"^@@ -(?P<OldStartLine>\d+)(,\d+)? \+(?P<NewStartLine>\d+)(,\d+)? @@", line) - if lines_changed: - if state != _DECLARED_FILE_PATH and state != _PROCESSING_CHUNK: - _log.error('Unexpected line change without file path ' - 'declaration: %r' % line) - old_diff_line = int(lines_changed.group('OldStartLine')) - new_diff_line = int(lines_changed.group('NewStartLine')) - state = _PROCESSING_CHUNK - continue - - if state == _PROCESSING_CHUNK: - if line.startswith('+'): - current_file.add_new_line(new_diff_line, line[1:]) - new_diff_line += 1 - elif line.startswith('-'): - current_file.add_deleted_line(old_diff_line, line[1:]) - old_diff_line += 1 - elif line.startswith(' '): - current_file.add_unchanged_line(old_diff_line, new_diff_line, line[1:]) - old_diff_line += 1 - new_diff_line += 1 - elif line == '\\ No newline at end of file': - # Nothing to do. We may still have some added lines. - pass - else: - _log.error('Unexpected diff format when parsing a ' - 'chunk: %r' % line) diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py deleted file mode 100644 index 7eb0eab..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/diff_parser_unittest.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest -import diff_parser -import re - - -class DiffParserTest(unittest.TestCase): - - _PATCH = '''diff --git a/WebCore/rendering/style/StyleFlexibleBoxData.h b/WebCore/rendering/style/StyleFlexibleBoxData.h -index f5d5e74..3b6aa92 100644 ---- a/WebCore/rendering/style/StyleFlexibleBoxData.h -+++ b/WebCore/rendering/style/StyleFlexibleBoxData.h -@@ -47,7 +47,6 @@ public: - - unsigned align : 3; // EBoxAlignment - unsigned pack: 3; // EBoxAlignment -- unsigned orient: 1; // EBoxOrient - unsigned lines : 1; // EBoxLines - - private: -diff --git a/WebCore/rendering/style/StyleRareInheritedData.cpp b/WebCore/rendering/style/StyleRareInheritedData.cpp -index ce21720..324929e 100644 ---- a/WebCore/rendering/style/StyleRareInheritedData.cpp -+++ b/WebCore/rendering/style/StyleRareInheritedData.cpp -@@ -39,6 +39,7 @@ StyleRareInheritedData::StyleRareInheritedData() - , textSizeAdjust(RenderStyle::initialTextSizeAdjust()) - , resize(RenderStyle::initialResize()) - , userSelect(RenderStyle::initialUserSelect()) -+ , boxOrient(RenderStyle::initialBoxOrient()) - { - } - -@@ -58,6 +59,7 @@ StyleRareInheritedData::StyleRareInheritedData(const StyleRareInheritedData& o) - , textSizeAdjust(o.textSizeAdjust) - , resize(o.resize) - , userSelect(o.userSelect) -+ , boxOrient(o.boxOrient) - { - } - -@@ -81,7 +83,8 @@ bool StyleRareInheritedData::operator==(const StyleRareInheritedData& o) const - && khtmlLineBreak == o.khtmlLineBreak - && textSizeAdjust == o.textSizeAdjust - && resize == o.resize -- && userSelect == o.userSelect; -+ && userSelect == o.userSelect -+ && boxOrient == o.boxOrient; - } - - bool StyleRareInheritedData::shadowDataEquivalent(const StyleRareInheritedData& o) const -diff --git a/LayoutTests/platform/mac/fast/flexbox/box-orient-button-expected.checksum b/LayoutTests/platform/mac/fast/flexbox/box-orient-button-expected.checksum -new file mode 100644 -index 0000000..6db26bd ---- /dev/null -+++ b/LayoutTests/platform/mac/fast/flexbox/box-orient-button-expected.checksum -@@ -0,0 +1 @@ -+61a373ee739673a9dcd7bac62b9f182e -\ No newline at end of file -''' - - def test_diff_parser(self, parser = None): - if not parser: - parser = diff_parser.DiffParser(self._PATCH.splitlines()) - self.assertEquals(3, len(parser.files)) - - self.assertTrue('WebCore/rendering/style/StyleFlexibleBoxData.h' in parser.files) - diff = parser.files['WebCore/rendering/style/StyleFlexibleBoxData.h'] - self.assertEquals(7, len(diff.lines)) - # The first two unchaged lines. - self.assertEquals((47, 47), diff.lines[0][0:2]) - self.assertEquals('', diff.lines[0][2]) - self.assertEquals((48, 48), diff.lines[1][0:2]) - self.assertEquals(' unsigned align : 3; // EBoxAlignment', diff.lines[1][2]) - # The deleted line - self.assertEquals((50, 0), diff.lines[3][0:2]) - self.assertEquals(' unsigned orient: 1; // EBoxOrient', diff.lines[3][2]) - - # The first file looks OK. Let's check the next, more complicated file. - self.assertTrue('WebCore/rendering/style/StyleRareInheritedData.cpp' in parser.files) - diff = parser.files['WebCore/rendering/style/StyleRareInheritedData.cpp'] - # There are 3 chunks. - self.assertEquals(7 + 7 + 9, len(diff.lines)) - # Around an added line. - self.assertEquals((60, 61), diff.lines[9][0:2]) - self.assertEquals((0, 62), diff.lines[10][0:2]) - self.assertEquals((61, 63), diff.lines[11][0:2]) - # Look through the last chunk, which contains both add's and delete's. - self.assertEquals((81, 83), diff.lines[14][0:2]) - self.assertEquals((82, 84), diff.lines[15][0:2]) - self.assertEquals((83, 85), diff.lines[16][0:2]) - self.assertEquals((84, 0), diff.lines[17][0:2]) - self.assertEquals((0, 86), diff.lines[18][0:2]) - self.assertEquals((0, 87), diff.lines[19][0:2]) - self.assertEquals((85, 88), diff.lines[20][0:2]) - self.assertEquals((86, 89), diff.lines[21][0:2]) - self.assertEquals((87, 90), diff.lines[22][0:2]) - - # Check if a newly added file is correctly handled. - diff = parser.files['LayoutTests/platform/mac/fast/flexbox/box-orient-button-expected.checksum'] - self.assertEquals(1, len(diff.lines)) - self.assertEquals((0, 1), diff.lines[0][0:2]) - - def test_git_mnemonicprefix(self): - p = re.compile(r' ([a|b])/') - - prefixes = [ - { 'a' : 'i', 'b' : 'w' }, # git-diff (compares the (i)ndex and the (w)ork tree) - { 'a' : 'c', 'b' : 'w' }, # git-diff HEAD (compares a (c)ommit and the (w)ork tree) - { 'a' : 'c', 'b' : 'i' }, # git diff --cached (compares a (c)ommit and the (i)ndex) - { 'a' : 'o', 'b' : 'w' }, # git-diff HEAD:file1 file2 (compares an (o)bject and a (w)ork tree entity) - { 'a' : '1', 'b' : '2' }, # git diff --no-index a b (compares two non-git things (1) and (2)) - ] - - for prefix in prefixes: - patch = p.sub(lambda x: " %s/" % prefix[x.group(1)], self._PATCH) - self.test_diff_parser(diff_parser.DiffParser(patch.splitlines())) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py deleted file mode 100644 index d39b8b4..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py +++ /dev/null @@ -1,915 +0,0 @@ -# Copyright (c) 2009, Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# Python module for interacting with an SCM system (like SVN or Git) - -import os -import re -import sys -import shutil - -from webkitpy.common.system.executive import Executive, run_command, ScriptError -from webkitpy.common.system.deprecated_logging import error, log -from webkitpy.common.memoized import memoized - - -def find_checkout_root(): - """Returns the current checkout root (as determined by default_scm(). - - Returns the absolute path to the top of the WebKit checkout, or None - if it cannot be determined. - - """ - scm_system = default_scm() - if scm_system: - return scm_system.checkout_root - return None - - -def default_scm(): - """Return the default SCM object as determined by the CWD and running code. - - Returns the default SCM object for the current working directory; if the - CWD is not in a checkout, then we attempt to figure out if the SCM module - itself is part of a checkout, and return that one. If neither is part of - a checkout, None is returned. - - """ - cwd = os.getcwd() - scm_system = detect_scm_system(cwd) - if not scm_system: - script_directory = os.path.dirname(os.path.abspath(__file__)) - scm_system = detect_scm_system(script_directory) - if scm_system: - log("The current directory (%s) is not a WebKit checkout, using %s" % (cwd, scm_system.checkout_root)) - else: - error("FATAL: Failed to determine the SCM system for either %s or %s" % (cwd, script_directory)) - return scm_system - - -def detect_scm_system(path): - absolute_path = os.path.abspath(path) - - if SVN.in_working_directory(absolute_path): - return SVN(cwd=absolute_path) - - if Git.in_working_directory(absolute_path): - return Git(cwd=absolute_path) - - return None - - -def first_non_empty_line_after_index(lines, index=0): - first_non_empty_line = index - for line in lines[index:]: - if re.match("^\s*$", line): - first_non_empty_line += 1 - else: - break - return first_non_empty_line - - -class CommitMessage: - def __init__(self, message): - self.message_lines = message[first_non_empty_line_after_index(message, 0):] - - def body(self, lstrip=False): - lines = self.message_lines[first_non_empty_line_after_index(self.message_lines, 1):] - if lstrip: - lines = [line.lstrip() for line in lines] - return "\n".join(lines) + "\n" - - def description(self, lstrip=False, strip_url=False): - line = self.message_lines[0] - if lstrip: - line = line.lstrip() - if strip_url: - line = re.sub("^(\s*)<.+> ", "\1", line) - return line - - def message(self): - return "\n".join(self.message_lines) + "\n" - - -class CheckoutNeedsUpdate(ScriptError): - def __init__(self, script_args, exit_code, output, cwd): - ScriptError.__init__(self, script_args=script_args, exit_code=exit_code, output=output, cwd=cwd) - - -def commit_error_handler(error): - if re.search("resource out of date", error.output): - raise CheckoutNeedsUpdate(script_args=error.script_args, exit_code=error.exit_code, output=error.output, cwd=error.cwd) - Executive.default_error_handler(error) - - -class AuthenticationError(Exception): - def __init__(self, server_host): - self.server_host = server_host - - -class AmbiguousCommitError(Exception): - def __init__(self, num_local_commits, working_directory_is_clean): - self.num_local_commits = num_local_commits - self.working_directory_is_clean = working_directory_is_clean - - -# SCM methods are expected to return paths relative to self.checkout_root. -class SCM: - def __init__(self, cwd): - self.cwd = cwd - self.checkout_root = self.find_checkout_root(self.cwd) - self.dryrun = False - - # A wrapper used by subclasses to create processes. - def run(self, args, cwd=None, input=None, error_handler=None, return_exit_code=False, return_stderr=True, decode_output=True): - # FIXME: We should set cwd appropriately. - # FIXME: We should use Executive. - return run_command(args, - cwd=cwd, - input=input, - error_handler=error_handler, - return_exit_code=return_exit_code, - return_stderr=return_stderr, - decode_output=decode_output) - - # SCM always returns repository relative path, but sometimes we need - # absolute paths to pass to rm, etc. - def absolute_path(self, repository_relative_path): - return os.path.join(self.checkout_root, repository_relative_path) - - # FIXME: This belongs in Checkout, not SCM. - def scripts_directory(self): - return os.path.join(self.checkout_root, "WebKitTools", "Scripts") - - # FIXME: This belongs in Checkout, not SCM. - def script_path(self, script_name): - return os.path.join(self.scripts_directory(), script_name) - - def ensure_clean_working_directory(self, force_clean): - if not force_clean and not self.working_directory_is_clean(): - # FIXME: Shouldn't this use cwd=self.checkout_root? - print self.run(self.status_command(), error_handler=Executive.ignore_error) - raise ScriptError(message="Working directory has modifications, pass --force-clean or --no-clean to continue.") - - log("Cleaning working directory") - self.clean_working_directory() - - def ensure_no_local_commits(self, force): - if not self.supports_local_commits(): - return - commits = self.local_commits() - if not len(commits): - return - if not force: - error("Working directory has local commits, pass --force-clean to continue.") - self.discard_local_commits() - - def run_status_and_extract_filenames(self, status_command, status_regexp): - filenames = [] - # We run with cwd=self.checkout_root so that returned-paths are root-relative. - for line in self.run(status_command, cwd=self.checkout_root).splitlines(): - match = re.search(status_regexp, line) - if not match: - continue - # status = match.group('status') - filename = match.group('filename') - filenames.append(filename) - return filenames - - def strip_r_from_svn_revision(self, svn_revision): - match = re.match("^r(?P<svn_revision>\d+)", unicode(svn_revision)) - if (match): - return match.group('svn_revision') - return svn_revision - - def svn_revision_from_commit_text(self, commit_text): - match = re.search(self.commit_success_regexp(), commit_text, re.MULTILINE) - return match.group('svn_revision') - - @staticmethod - def _subclass_must_implement(): - raise NotImplementedError("subclasses must implement") - - @staticmethod - def in_working_directory(path): - SCM._subclass_must_implement() - - @staticmethod - def find_checkout_root(path): - SCM._subclass_must_implement() - - @staticmethod - def commit_success_regexp(): - SCM._subclass_must_implement() - - def working_directory_is_clean(self): - self._subclass_must_implement() - - def clean_working_directory(self): - self._subclass_must_implement() - - def status_command(self): - self._subclass_must_implement() - - def add(self, path, return_exit_code=False): - self._subclass_must_implement() - - def delete(self, path): - self._subclass_must_implement() - - def changed_files(self, git_commit=None): - self._subclass_must_implement() - - def changed_files_for_revision(self, revision): - self._subclass_must_implement() - - def revisions_changing_file(self, path, limit=5): - self._subclass_must_implement() - - def added_files(self): - self._subclass_must_implement() - - def conflicted_files(self): - self._subclass_must_implement() - - def display_name(self): - self._subclass_must_implement() - - def create_patch(self, git_commit=None, changed_files=[]): - self._subclass_must_implement() - - def committer_email_for_revision(self, revision): - self._subclass_must_implement() - - def contents_at_revision(self, path, revision): - self._subclass_must_implement() - - def diff_for_revision(self, revision): - self._subclass_must_implement() - - def diff_for_file(self, path, log=None): - self._subclass_must_implement() - - def show_head(self, path): - self._subclass_must_implement() - - def apply_reverse_diff(self, revision): - self._subclass_must_implement() - - def revert_files(self, file_paths): - self._subclass_must_implement() - - def commit_with_message(self, message, username=None, git_commit=None, force_squash=False): - self._subclass_must_implement() - - def svn_commit_log(self, svn_revision): - self._subclass_must_implement() - - def last_svn_commit_log(self): - self._subclass_must_implement() - - # Subclasses must indicate if they support local commits, - # but the SCM baseclass will only call local_commits methods when this is true. - @staticmethod - def supports_local_commits(): - SCM._subclass_must_implement() - - def remote_merge_base(): - SCM._subclass_must_implement() - - def commit_locally_with_message(self, message): - error("Your source control manager does not support local commits.") - - def discard_local_commits(self): - pass - - def local_commits(self): - return [] - - -class SVN(SCM): - # FIXME: We should move these values to a WebKit-specific config. file. - svn_server_host = "svn.webkit.org" - svn_server_realm = "<http://svn.webkit.org:80> Mac OS Forge" - - def __init__(self, cwd): - SCM.__init__(self, cwd) - self._bogus_dir = None - - @staticmethod - def in_working_directory(path): - return os.path.isdir(os.path.join(path, '.svn')) - - @classmethod - def find_uuid(cls, path): - if not cls.in_working_directory(path): - return None - return cls.value_from_svn_info(path, 'Repository UUID') - - @classmethod - def value_from_svn_info(cls, path, field_name): - svn_info_args = ['svn', 'info', path] - info_output = run_command(svn_info_args).rstrip() - match = re.search("^%s: (?P<value>.+)$" % field_name, info_output, re.MULTILINE) - if not match: - raise ScriptError(script_args=svn_info_args, message='svn info did not contain a %s.' % field_name) - return match.group('value') - - @staticmethod - def find_checkout_root(path): - uuid = SVN.find_uuid(path) - # If |path| is not in a working directory, we're supposed to return |path|. - if not uuid: - return path - # Search up the directory hierarchy until we find a different UUID. - last_path = None - while True: - if uuid != SVN.find_uuid(path): - return last_path - last_path = path - (path, last_component) = os.path.split(path) - if last_path == path: - return None - - @staticmethod - def commit_success_regexp(): - return "^Committed revision (?P<svn_revision>\d+)\.$" - - def has_authorization_for_realm(self, realm=svn_server_realm, home_directory=os.getenv("HOME")): - # Assumes find and grep are installed. - if not os.path.isdir(os.path.join(home_directory, ".subversion")): - return False - find_args = ["find", ".subversion", "-type", "f", "-exec", "grep", "-q", realm, "{}", ";", "-print"]; - find_output = self.run(find_args, cwd=home_directory, error_handler=Executive.ignore_error).rstrip() - return find_output and os.path.isfile(os.path.join(home_directory, find_output)) - - @memoized - def svn_version(self): - return self.run(['svn', '--version', '--quiet']) - - def working_directory_is_clean(self): - return self.run(["svn", "diff"], cwd=self.checkout_root, decode_output=False) == "" - - def clean_working_directory(self): - # Make sure there are no locks lying around from a previously aborted svn invocation. - # This is slightly dangerous, as it's possible the user is running another svn process - # on this checkout at the same time. However, it's much more likely that we're running - # under windows and svn just sucks (or the user interrupted svn and it failed to clean up). - self.run(["svn", "cleanup"], cwd=self.checkout_root) - - # svn revert -R is not as awesome as git reset --hard. - # It will leave added files around, causing later svn update - # calls to fail on the bots. We make this mirror git reset --hard - # by deleting any added files as well. - added_files = reversed(sorted(self.added_files())) - # added_files() returns directories for SVN, we walk the files in reverse path - # length order so that we remove files before we try to remove the directories. - self.run(["svn", "revert", "-R", "."], cwd=self.checkout_root) - for path in added_files: - # This is robust against cwd != self.checkout_root - absolute_path = self.absolute_path(path) - # Completely lame that there is no easy way to remove both types with one call. - if os.path.isdir(path): - os.rmdir(absolute_path) - else: - os.remove(absolute_path) - - def status_command(self): - return ['svn', 'status'] - - def _status_regexp(self, expected_types): - field_count = 6 if self.svn_version() > "1.6" else 5 - return "^(?P<status>[%s]).{%s} (?P<filename>.+)$" % (expected_types, field_count) - - def _add_parent_directories(self, path): - """Does 'svn add' to the path and its parents.""" - if self.in_working_directory(path): - return - dirname = os.path.dirname(path) - # We have dirname directry - ensure it added. - if dirname != path: - self._add_parent_directories(dirname) - self.add(path) - - def add(self, path, return_exit_code=False): - self._add_parent_directories(os.path.dirname(os.path.abspath(path))) - return self.run(["svn", "add", path], return_exit_code=return_exit_code) - - def delete(self, path): - parent, base = os.path.split(os.path.abspath(path)) - return self.run(["svn", "delete", "--force", base], cwd=parent) - - def changed_files(self, git_commit=None): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("ACDMR")) - - def changed_files_for_revision(self, revision): - # As far as I can tell svn diff --summarize output looks just like svn status output. - # No file contents printed, thus utf-8 auto-decoding in self.run is fine. - status_command = ["svn", "diff", "--summarize", "-c", revision] - return self.run_status_and_extract_filenames(status_command, self._status_regexp("ACDMR")) - - def revisions_changing_file(self, path, limit=5): - revisions = [] - # svn log will exit(1) (and thus self.run will raise) if the path does not exist. - log_command = ['svn', 'log', '--quiet', '--limit=%s' % limit, path] - for line in self.run(log_command, cwd=self.checkout_root).splitlines(): - match = re.search('^r(?P<revision>\d+) ', line) - if not match: - continue - revisions.append(int(match.group('revision'))) - return revisions - - def conflicted_files(self): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("C")) - - def added_files(self): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("A")) - - def deleted_files(self): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("D")) - - @staticmethod - def supports_local_commits(): - return False - - def display_name(self): - return "svn" - - # FIXME: This method should be on Checkout. - def create_patch(self, git_commit=None, changed_files=[]): - """Returns a byte array (str()) representing the patch file. - Patch files are effectively binary since they may contain - files of multiple different encodings.""" - return self.run([self.script_path("svn-create-patch")] + changed_files, - cwd=self.checkout_root, return_stderr=False, - decode_output=False) - - def committer_email_for_revision(self, revision): - return self.run(["svn", "propget", "svn:author", "--revprop", "-r", revision]).rstrip() - - def contents_at_revision(self, path, revision): - """Returns a byte array (str()) containing the contents - of path @ revision in the repository.""" - remote_path = "%s/%s" % (self._repository_url(), path) - return self.run(["svn", "cat", "-r", revision, remote_path], decode_output=False) - - def diff_for_revision(self, revision): - # FIXME: This should probably use cwd=self.checkout_root - return self.run(['svn', 'diff', '-c', revision]) - - def _bogus_dir_name(self): - if sys.platform.startswith("win"): - parent_dir = tempfile.gettempdir() - else: - parent_dir = sys.path[0] # tempdir is not secure. - return os.path.join(parent_dir, "temp_svn_config") - - def _setup_bogus_dir(self, log): - self._bogus_dir = self._bogus_dir_name() - if not os.path.exists(self._bogus_dir): - os.mkdir(self._bogus_dir) - self._delete_bogus_dir = True - else: - self._delete_bogus_dir = False - if log: - log.debug(' Html: temp config dir: "%s".', self._bogus_dir) - - def _teardown_bogus_dir(self, log): - if self._delete_bogus_dir: - shutil.rmtree(self._bogus_dir, True) - if log: - log.debug(' Html: removed temp config dir: "%s".', self._bogus_dir) - self._bogus_dir = None - - def diff_for_file(self, path, log=None): - self._setup_bogus_dir(log) - try: - args = ['svn', 'diff'] - if self._bogus_dir: - args += ['--config-dir', self._bogus_dir] - args.append(path) - return self.run(args) - finally: - self._teardown_bogus_dir(log) - - def show_head(self, path): - return self.run(['svn', 'cat', '-r', 'BASE', path], decode_output=False) - - def _repository_url(self): - return self.value_from_svn_info(self.checkout_root, 'URL') - - def apply_reverse_diff(self, revision): - # '-c -revision' applies the inverse diff of 'revision' - svn_merge_args = ['svn', 'merge', '--non-interactive', '-c', '-%s' % revision, self._repository_url()] - log("WARNING: svn merge has been known to take more than 10 minutes to complete. It is recommended you use git for rollouts.") - log("Running '%s'" % " ".join(svn_merge_args)) - # FIXME: Should this use cwd=self.checkout_root? - self.run(svn_merge_args) - - def revert_files(self, file_paths): - # FIXME: This should probably use cwd=self.checkout_root. - self.run(['svn', 'revert'] + file_paths) - - def commit_with_message(self, message, username=None, git_commit=None, force_squash=False): - # git-commit and force are not used by SVN. - if self.dryrun: - # Return a string which looks like a commit so that things which parse this output will succeed. - return "Dry run, no commit.\nCommitted revision 0." - - svn_commit_args = ["svn", "commit"] - - if not username and not self.has_authorization_for_realm(): - raise AuthenticationError(self.svn_server_host) - if username: - svn_commit_args.extend(["--username", username]) - - svn_commit_args.extend(["-m", message]) - # FIXME: Should this use cwd=self.checkout_root? - return self.run(svn_commit_args, error_handler=commit_error_handler) - - def svn_commit_log(self, svn_revision): - svn_revision = self.strip_r_from_svn_revision(svn_revision) - return self.run(['svn', 'log', '--non-interactive', '--revision', svn_revision]) - - def last_svn_commit_log(self): - # BASE is the checkout revision, HEAD is the remote repository revision - # http://svnbook.red-bean.com/en/1.0/ch03s03.html - return self.svn_commit_log('BASE') - - def propset(self, pname, pvalue, path): - dir, base = os.path.split(path) - return self.run(['svn', 'pset', pname, pvalue, base], cwd=dir) - - def propget(self, pname, path): - dir, base = os.path.split(path) - return self.run(['svn', 'pget', pname, base], cwd=dir).encode('utf-8').rstrip("\n") - - -# All git-specific logic should go here. -class Git(SCM): - def __init__(self, cwd): - SCM.__init__(self, cwd) - - @classmethod - def in_working_directory(cls, path): - return run_command(['git', 'rev-parse', '--is-inside-work-tree'], cwd=path, error_handler=Executive.ignore_error).rstrip() == "true" - - @classmethod - def find_checkout_root(cls, path): - # "git rev-parse --show-cdup" would be another way to get to the root - (checkout_root, dot_git) = os.path.split(run_command(['git', 'rev-parse', '--git-dir'], cwd=(path or "./"))) - # If we were using 2.6 # checkout_root = os.path.relpath(checkout_root, path) - if not os.path.isabs(checkout_root): # Sometimes git returns relative paths - checkout_root = os.path.join(path, checkout_root) - return checkout_root - - @classmethod - def to_object_name(cls, filepath): - root_end_with_slash = os.path.join(cls.find_checkout_root(os.path.dirname(filepath)), '') - return filepath.replace(root_end_with_slash, '') - - @classmethod - def read_git_config(cls, key): - # FIXME: This should probably use cwd=self.checkout_root. - # Pass --get-all for cases where the config has multiple values - return run_command(["git", "config", "--get-all", key], - error_handler=Executive.ignore_error).rstrip('\n') - - @staticmethod - def commit_success_regexp(): - return "^Committed r(?P<svn_revision>\d+)$" - - def discard_local_commits(self): - # FIXME: This should probably use cwd=self.checkout_root - self.run(['git', 'reset', '--hard', self.remote_branch_ref()]) - - def local_commits(self): - # FIXME: This should probably use cwd=self.checkout_root - return self.run(['git', 'log', '--pretty=oneline', 'HEAD...' + self.remote_branch_ref()]).splitlines() - - def rebase_in_progress(self): - return os.path.exists(os.path.join(self.checkout_root, '.git/rebase-apply')) - - def working_directory_is_clean(self): - # FIXME: This should probably use cwd=self.checkout_root - return self.run(['git', 'diff', 'HEAD', '--name-only']) == "" - - def clean_working_directory(self): - # FIXME: These should probably use cwd=self.checkout_root. - # Could run git clean here too, but that wouldn't match working_directory_is_clean - self.run(['git', 'reset', '--hard', 'HEAD']) - # Aborting rebase even though this does not match working_directory_is_clean - if self.rebase_in_progress(): - self.run(['git', 'rebase', '--abort']) - - def status_command(self): - # git status returns non-zero when there are changes, so we use git diff name --name-status HEAD instead. - # No file contents printed, thus utf-8 autodecoding in self.run is fine. - return ["git", "diff", "--name-status", "HEAD"] - - def _status_regexp(self, expected_types): - return '^(?P<status>[%s])\t(?P<filename>.+)$' % expected_types - - def add(self, path, return_exit_code=False): - return self.run(["git", "add", path], return_exit_code=return_exit_code) - - def delete(self, path): - return self.run(["git", "rm", "-f", path]) - - def _assert_synced(self): - if len(run_command(['git', 'rev-list', '--max-count=1', self.remote_branch_ref(), '^HEAD'])): - raise ScriptError(message="Not fully merged/rebased to %s. This branch needs to be synced first." % self.remote_branch_ref()) - - def merge_base(self, git_commit): - if git_commit: - # Special-case HEAD.. to mean working-copy changes only. - if git_commit.upper() == 'HEAD..': - return 'HEAD' - - if '..' not in git_commit: - git_commit = git_commit + "^.." + git_commit - return git_commit - - self._assert_synced() - return self.remote_merge_base() - - def changed_files(self, git_commit=None): - status_command = ['git', 'diff', '-r', '--name-status', '-C', '-M', "--no-ext-diff", "--full-index", self.merge_base(git_commit)] - return self.run_status_and_extract_filenames(status_command, self._status_regexp("ADM")) - - def _changes_files_for_commit(self, git_commit): - # --pretty="format:" makes git show not print the commit log header, - changed_files = self.run(["git", "show", "--pretty=format:", "--name-only", git_commit]).splitlines() - # instead it just prints a blank line at the top, so we skip the blank line: - return changed_files[1:] - - def changed_files_for_revision(self, revision): - commit_id = self.git_commit_from_svn_revision(revision) - return self._changes_files_for_commit(commit_id) - - def revisions_changing_file(self, path, limit=5): - # git rev-list head --remove-empty --limit=5 -- path would be equivalent. - commit_ids = self.run(["git", "log", "--remove-empty", "--pretty=format:%H", "-%s" % limit, "--", path]).splitlines() - return filter(lambda revision: revision, map(self.svn_revision_from_git_commit, commit_ids)) - - def conflicted_files(self): - # We do not need to pass decode_output for this diff command - # as we're passing --name-status which does not output any data. - status_command = ['git', 'diff', '--name-status', '-C', '-M', '--diff-filter=U'] - return self.run_status_and_extract_filenames(status_command, self._status_regexp("U")) - - def added_files(self): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("A")) - - def deleted_files(self): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("D")) - - @staticmethod - def supports_local_commits(): - return True - - def display_name(self): - return "git" - - def create_patch(self, git_commit=None, changed_files=[]): - """Returns a byte array (str()) representing the patch file. - Patch files are effectively binary since they may contain - files of multiple different encodings.""" - return self.run(['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "-M", self.merge_base(git_commit), "--"] + changed_files, decode_output=False, cwd=self.checkout_root) - - def _run_git_svn_find_rev(self, arg): - # git svn find-rev always exits 0, even when the revision or commit is not found. - return self.run(['git', 'svn', 'find-rev', arg], cwd=self.checkout_root).rstrip() - - def _string_to_int_or_none(self, string): - try: - return int(string) - except ValueError, e: - return None - - @memoized - def git_commit_from_svn_revision(self, svn_revision): - git_commit = self._run_git_svn_find_rev('r%s' % svn_revision) - if not git_commit: - # FIXME: Alternatively we could offer to update the checkout? Or return None? - raise ScriptError(message='Failed to find git commit for revision %s, your checkout likely needs an update.' % svn_revision) - return git_commit - - @memoized - def svn_revision_from_git_commit(self, git_commit): - svn_revision = self._run_git_svn_find_rev(git_commit) - return self._string_to_int_or_none(svn_revision) - - def contents_at_revision(self, path, revision): - """Returns a byte array (str()) containing the contents - of path @ revision in the repository.""" - return self.run(["git", "show", "%s:%s" % (self.git_commit_from_svn_revision(revision), path)], decode_output=False) - - def diff_for_revision(self, revision): - git_commit = self.git_commit_from_svn_revision(revision) - return self.create_patch(git_commit) - - def diff_for_file(self, path, log=None): - return self.run(['git', 'diff', 'HEAD', '--', path]) - - def show_head(self, path): - return self.run(['git', 'show', 'HEAD:' + self.to_object_name(path)], decode_output=False) - - def committer_email_for_revision(self, revision): - git_commit = self.git_commit_from_svn_revision(revision) - committer_email = self.run(["git", "log", "-1", "--pretty=format:%ce", git_commit]) - # Git adds an extra @repository_hash to the end of every committer email, remove it: - return committer_email.rsplit("@", 1)[0] - - def apply_reverse_diff(self, revision): - # Assume the revision is an svn revision. - git_commit = self.git_commit_from_svn_revision(revision) - # I think this will always fail due to ChangeLogs. - self.run(['git', 'revert', '--no-commit', git_commit], error_handler=Executive.ignore_error) - - def revert_files(self, file_paths): - self.run(['git', 'checkout', 'HEAD'] + file_paths) - - def _assert_can_squash(self, working_directory_is_clean): - squash = Git.read_git_config('webkit-patch.commit-should-always-squash') - should_squash = squash and squash.lower() == "true" - - if not should_squash: - # Only warn if there are actually multiple commits to squash. - num_local_commits = len(self.local_commits()) - if num_local_commits > 1 or (num_local_commits > 0 and not working_directory_is_clean): - raise AmbiguousCommitError(num_local_commits, working_directory_is_clean) - - def commit_with_message(self, message, username=None, git_commit=None, force_squash=False): - # Username is ignored during Git commits. - working_directory_is_clean = self.working_directory_is_clean() - - if git_commit: - # Special-case HEAD.. to mean working-copy changes only. - if git_commit.upper() == 'HEAD..': - if working_directory_is_clean: - raise ScriptError(message="The working copy is not modified. --git-commit=HEAD.. only commits working copy changes.") - self.commit_locally_with_message(message) - return self._commit_on_branch(message, 'HEAD') - - # Need working directory changes to be committed so we can checkout the merge branch. - if not working_directory_is_clean: - # FIXME: webkit-patch land will modify the ChangeLogs to correct the reviewer. - # That will modify the working-copy and cause us to hit this error. - # The ChangeLog modification could be made to modify the existing local commit. - raise ScriptError(message="Working copy is modified. Cannot commit individual git_commits.") - return self._commit_on_branch(message, git_commit) - - if not force_squash: - self._assert_can_squash(working_directory_is_clean) - self._assert_synced() - self.run(['git', 'reset', '--soft', self.remote_branch_ref()]) - self.commit_locally_with_message(message) - return self.push_local_commits_to_server() - - def _commit_on_branch(self, message, git_commit): - branch_ref = self.run(['git', 'symbolic-ref', 'HEAD']).strip() - branch_name = branch_ref.replace('refs/heads/', '') - commit_ids = self.commit_ids_from_commitish_arguments([git_commit]) - - # We want to squash all this branch's commits into one commit with the proper description. - # We do this by doing a "merge --squash" into a new commit branch, then dcommitting that. - MERGE_BRANCH_NAME = 'webkit-patch-land' - self.delete_branch(MERGE_BRANCH_NAME) - - # We might be in a directory that's present in this branch but not in the - # trunk. Move up to the top of the tree so that git commands that expect a - # valid CWD won't fail after we check out the merge branch. - os.chdir(self.checkout_root) - - # Stuff our change into the merge branch. - # We wrap in a try...finally block so if anything goes wrong, we clean up the branches. - commit_succeeded = True - try: - self.run(['git', 'checkout', '-q', '-b', MERGE_BRANCH_NAME, self.remote_branch_ref()]) - - for commit in commit_ids: - # We're on a different branch now, so convert "head" to the branch name. - commit = re.sub(r'(?i)head', branch_name, commit) - # FIXME: Once changed_files and create_patch are modified to separately handle each - # commit in a commit range, commit each cherry pick so they'll get dcommitted separately. - self.run(['git', 'cherry-pick', '--no-commit', commit]) - - self.run(['git', 'commit', '-m', message]) - output = self.push_local_commits_to_server() - except Exception, e: - log("COMMIT FAILED: " + str(e)) - output = "Commit failed." - commit_succeeded = False - finally: - # And then swap back to the original branch and clean up. - self.clean_working_directory() - self.run(['git', 'checkout', '-q', branch_name]) - self.delete_branch(MERGE_BRANCH_NAME) - - return output - - def svn_commit_log(self, svn_revision): - svn_revision = self.strip_r_from_svn_revision(svn_revision) - return self.run(['git', 'svn', 'log', '-r', svn_revision]) - - def last_svn_commit_log(self): - return self.run(['git', 'svn', 'log', '--limit=1']) - - # Git-specific methods: - def _branch_ref_exists(self, branch_ref): - return self.run(['git', 'show-ref', '--quiet', '--verify', branch_ref], return_exit_code=True) == 0 - - def delete_branch(self, branch_name): - if self._branch_ref_exists('refs/heads/' + branch_name): - self.run(['git', 'branch', '-D', branch_name]) - - def remote_merge_base(self): - return self.run(['git', 'merge-base', self.remote_branch_ref(), 'HEAD']).strip() - - def remote_branch_ref(self): - # Use references so that we can avoid collisions, e.g. we don't want to operate on refs/heads/trunk if it exists. - remote_branch_refs = Git.read_git_config('svn-remote.svn.fetch') - if not remote_branch_refs: - remote_master_ref = 'refs/remotes/origin/master' - if not self._branch_ref_exists(remote_master_ref): - raise ScriptError(message="Can't find a branch to diff against. svn-remote.svn.fetch is not in the git config and %s does not exist" % remote_master_ref) - return remote_master_ref - - # FIXME: What's the right behavior when there are multiple svn-remotes listed? - # For now, just use the first one. - first_remote_branch_ref = remote_branch_refs.split('\n')[0] - return first_remote_branch_ref.split(':')[1] - - def commit_locally_with_message(self, message): - self.run(['git', 'commit', '--all', '-F', '-'], input=message) - - def push_local_commits_to_server(self): - dcommit_command = ['git', 'svn', 'dcommit'] - if self.dryrun: - dcommit_command.append('--dry-run') - output = self.run(dcommit_command, error_handler=commit_error_handler) - # Return a string which looks like a commit so that things which parse this output will succeed. - if self.dryrun: - output += "\nCommitted r0" - return output - - # This function supports the following argument formats: - # no args : rev-list trunk..HEAD - # A..B : rev-list A..B - # A...B : error! - # A B : [A, B] (different from git diff, which would use "rev-list A..B") - def commit_ids_from_commitish_arguments(self, args): - if not len(args): - args.append('%s..HEAD' % self.remote_branch_ref()) - - commit_ids = [] - for commitish in args: - if '...' in commitish: - raise ScriptError(message="'...' is not supported (found in '%s'). Did you mean '..'?" % commitish) - elif '..' in commitish: - commit_ids += reversed(self.run(['git', 'rev-list', commitish]).splitlines()) - else: - # Turn single commits or branch or tag names into commit ids. - commit_ids += self.run(['git', 'rev-parse', '--revs-only', commitish]).splitlines() - return commit_ids - - def commit_message_for_local_commit(self, commit_id): - commit_lines = self.run(['git', 'cat-file', 'commit', commit_id]).splitlines() - - # Skip the git headers. - first_line_after_headers = 0 - for line in commit_lines: - first_line_after_headers += 1 - if line == "": - break - return CommitMessage(commit_lines[first_line_after_headers:]) - - def files_changed_summary_for_commit(self, commit_id): - return self.run(['git', 'diff-tree', '--shortstat', '--no-commit-id', commit_id]) diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py deleted file mode 100644 index 46a2acf..0000000 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py +++ /dev/null @@ -1,1291 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# Copyright (C) 2009 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from __future__ import with_statement - -import base64 -import codecs -import getpass -import os -import os.path -import re -import stat -import sys -import subprocess -import tempfile -import unittest -import urllib -import shutil - -from datetime import date -from webkitpy.common.checkout.api import Checkout -from webkitpy.common.checkout.scm import detect_scm_system, SCM, SVN, CheckoutNeedsUpdate, commit_error_handler, AuthenticationError, AmbiguousCommitError, find_checkout_root, default_scm -from webkitpy.common.config.committers import Committer # FIXME: This should not be needed -from webkitpy.common.net.bugzilla import Attachment # FIXME: This should not be needed -from webkitpy.common.system.executive import Executive, run_command, ScriptError -from webkitpy.common.system.outputcapture import OutputCapture - -# Eventually we will want to write tests which work for both scms. (like update_webkit, changed_files, etc.) -# Perhaps through some SCMTest base-class which both SVNTest and GitTest inherit from. - -# FIXME: This should be unified into one of the executive.py commands! -# Callers could use run_and_throw_if_fail(args, cwd=cwd, quiet=True) -def run_silent(args, cwd=None): - # Note: Not thread safe: http://bugs.python.org/issue2320 - process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) - process.communicate() # ignore output - exit_code = process.wait() - if exit_code: - raise ScriptError('Failed to run "%s" exit_code: %d cwd: %s' % (args, exit_code, cwd)) - - -def write_into_file_at_path(file_path, contents, encoding="utf-8"): - if encoding: - with codecs.open(file_path, "w", encoding) as file: - file.write(contents) - else: - with open(file_path, "w") as file: - file.write(contents) - - -def read_from_path(file_path, encoding="utf-8"): - with codecs.open(file_path, "r", encoding) as file: - return file.read() - - -def _make_diff(command, *args): - # We use this wrapper to disable output decoding. diffs should be treated as - # binary files since they may include text files of multiple differnet encodings. - return run_command([command, "diff"] + list(args), decode_output=False) - - -def _svn_diff(*args): - return _make_diff("svn", *args) - - -def _git_diff(*args): - return _make_diff("git", *args) - - -# Exists to share svn repository creation code between the git and svn tests -class SVNTestRepository: - @classmethod - def _svn_add(cls, path): - run_command(["svn", "add", path]) - - @classmethod - def _svn_commit(cls, message): - run_command(["svn", "commit", "--quiet", "--message", message]) - - @classmethod - def _setup_test_commits(cls, test_object): - # Add some test commits - os.chdir(test_object.svn_checkout_path) - - write_into_file_at_path("test_file", "test1") - cls._svn_add("test_file") - cls._svn_commit("initial commit") - - write_into_file_at_path("test_file", "test1test2") - # This used to be the last commit, but doing so broke - # GitTest.test_apply_git_patch which use the inverse diff of the last commit. - # svn-apply fails to remove directories in Git, see: - # https://bugs.webkit.org/show_bug.cgi?id=34871 - os.mkdir("test_dir") - # Slash should always be the right path separator since we use cygwin on Windows. - test_file3_path = "test_dir/test_file3" - write_into_file_at_path(test_file3_path, "third file") - cls._svn_add("test_dir") - cls._svn_commit("second commit") - - write_into_file_at_path("test_file", "test1test2test3\n") - write_into_file_at_path("test_file2", "second file") - cls._svn_add("test_file2") - cls._svn_commit("third commit") - - # This 4th commit is used to make sure that our patch file handling - # code correctly treats patches as binary and does not attempt to - # decode them assuming they're utf-8. - write_into_file_at_path("test_file", u"latin1 test: \u00A0\n", "latin1") - write_into_file_at_path("test_file2", u"utf-8 test: \u00A0\n", "utf-8") - cls._svn_commit("fourth commit") - - # svn does not seem to update after commit as I would expect. - run_command(['svn', 'update']) - - @classmethod - def setup(cls, test_object): - # Create an test SVN repository - test_object.svn_repo_path = tempfile.mkdtemp(suffix="svn_test_repo") - test_object.svn_repo_url = "file://%s" % test_object.svn_repo_path # Not sure this will work on windows - # git svn complains if we don't pass --pre-1.5-compatible, not sure why: - # Expected FS format '2'; found format '3' at /usr/local/libexec/git-core//git-svn line 1477 - run_command(['svnadmin', 'create', '--pre-1.5-compatible', test_object.svn_repo_path]) - - # Create a test svn checkout - test_object.svn_checkout_path = tempfile.mkdtemp(suffix="svn_test_checkout") - run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url, test_object.svn_checkout_path]) - - # Create and checkout a trunk dir to match the standard svn configuration to match git-svn's expectations - os.chdir(test_object.svn_checkout_path) - os.mkdir('trunk') - cls._svn_add('trunk') - # We can add tags and branches as well if we ever need to test those. - cls._svn_commit('add trunk') - - # Change directory out of the svn checkout so we can delete the checkout directory. - # _setup_test_commits will CD back to the svn checkout directory. - os.chdir('/') - run_command(['rm', '-rf', test_object.svn_checkout_path]) - run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url + '/trunk', test_object.svn_checkout_path]) - - cls._setup_test_commits(test_object) - - @classmethod - def tear_down(cls, test_object): - run_command(['rm', '-rf', test_object.svn_repo_path]) - run_command(['rm', '-rf', test_object.svn_checkout_path]) - - # Now that we've deleted the checkout paths, cwddir may be invalid - # Change back to a valid directory so that later calls to os.getcwd() do not fail. - os.chdir(detect_scm_system(os.path.dirname(__file__)).checkout_root) - - -class StandaloneFunctionsTest(unittest.TestCase): - """This class tests any standalone/top-level functions in the package.""" - def setUp(self): - self.orig_cwd = os.path.abspath(os.getcwd()) - self.orig_abspath = os.path.abspath - - # We capture but ignore the output from stderr to reduce unwanted - # logging. - self.output = OutputCapture() - self.output.capture_output() - - def tearDown(self): - os.chdir(self.orig_cwd) - os.path.abspath = self.orig_abspath - self.output.restore_output() - - def test_find_checkout_root(self): - # Test from inside the tree. - os.chdir(sys.path[0]) - dir = find_checkout_root() - self.assertNotEqual(dir, None) - self.assertTrue(os.path.exists(dir)) - - # Test from outside the tree. - os.chdir(os.path.expanduser("~")) - dir = find_checkout_root() - self.assertNotEqual(dir, None) - self.assertTrue(os.path.exists(dir)) - - # Mock out abspath() to test being not in a checkout at all. - os.path.abspath = lambda x: "/" - self.assertRaises(SystemExit, find_checkout_root) - os.path.abspath = self.orig_abspath - - def test_default_scm(self): - # Test from inside the tree. - os.chdir(sys.path[0]) - scm = default_scm() - self.assertNotEqual(scm, None) - - # Test from outside the tree. - os.chdir(os.path.expanduser("~")) - dir = find_checkout_root() - self.assertNotEqual(dir, None) - - # Mock out abspath() to test being not in a checkout at all. - os.path.abspath = lambda x: "/" - self.assertRaises(SystemExit, default_scm) - os.path.abspath = self.orig_abspath - -# For testing the SCM baseclass directly. -class SCMClassTests(unittest.TestCase): - def setUp(self): - self.dev_null = open(os.devnull, "w") # Used to make our Popen calls quiet. - - def tearDown(self): - self.dev_null.close() - - def test_run_command_with_pipe(self): - input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) - self.assertEqual(run_command(['grep', 'bar'], input=input_process.stdout), "bar\n") - - # Test the non-pipe case too: - self.assertEqual(run_command(['grep', 'bar'], input="foo\nbar"), "bar\n") - - command_returns_non_zero = ['/bin/sh', '--invalid-option'] - # Test when the input pipe process fails. - input_process = subprocess.Popen(command_returns_non_zero, stdout=subprocess.PIPE, stderr=self.dev_null) - self.assertTrue(input_process.poll() != 0) - self.assertRaises(ScriptError, run_command, ['grep', 'bar'], input=input_process.stdout) - - # Test when the run_command process fails. - input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) # grep shows usage and calls exit(2) when called w/o arguments. - self.assertRaises(ScriptError, run_command, command_returns_non_zero, input=input_process.stdout) - - def test_error_handlers(self): - git_failure_message="Merge conflict during commit: Your file or directory 'WebCore/ChangeLog' is probably out-of-date: resource out of date; try updating at /usr/local/libexec/git-core//git-svn line 469" - svn_failure_message="""svn: Commit failed (details follow): -svn: File or directory 'ChangeLog' is out of date; try updating -svn: resource out of date; try updating -""" - command_does_not_exist = ['does_not_exist', 'invalid_option'] - self.assertRaises(OSError, run_command, command_does_not_exist) - self.assertRaises(OSError, run_command, command_does_not_exist, error_handler=Executive.ignore_error) - - command_returns_non_zero = ['/bin/sh', '--invalid-option'] - self.assertRaises(ScriptError, run_command, command_returns_non_zero) - # Check if returns error text: - self.assertTrue(run_command(command_returns_non_zero, error_handler=Executive.ignore_error)) - - self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=git_failure_message)) - self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=svn_failure_message)) - self.assertRaises(ScriptError, commit_error_handler, ScriptError(output='blah blah blah')) - - -# GitTest and SVNTest inherit from this so any test_ methods here will be run once for this class and then once for each subclass. -class SCMTest(unittest.TestCase): - def _create_patch(self, patch_contents): - # FIXME: This code is brittle if the Attachment API changes. - attachment = Attachment({"bug_id": 12345}, None) - attachment.contents = lambda: patch_contents - - joe_cool = Committer(name="Joe Cool", email_or_emails=None) - attachment.reviewer = lambda: joe_cool - - return attachment - - def _setup_webkittools_scripts_symlink(self, local_scm): - webkit_scm = detect_scm_system(os.path.dirname(os.path.abspath(__file__))) - webkit_scripts_directory = webkit_scm.scripts_directory() - local_scripts_directory = local_scm.scripts_directory() - os.mkdir(os.path.dirname(local_scripts_directory)) - os.symlink(webkit_scripts_directory, local_scripts_directory) - - # Tests which both GitTest and SVNTest should run. - # FIXME: There must be a simpler way to add these w/o adding a wrapper method to both subclasses - - def _shared_test_changed_files(self): - write_into_file_at_path("test_file", "changed content") - self.assertEqual(self.scm.changed_files(), ["test_file"]) - write_into_file_at_path("test_dir/test_file3", "new stuff") - self.assertEqual(self.scm.changed_files(), ["test_dir/test_file3", "test_file"]) - old_cwd = os.getcwd() - os.chdir("test_dir") - # Validate that changed_files does not change with our cwd, see bug 37015. - self.assertEqual(self.scm.changed_files(), ["test_dir/test_file3", "test_file"]) - os.chdir(old_cwd) - - def _shared_test_added_files(self): - write_into_file_at_path("test_file", "changed content") - self.assertEqual(self.scm.added_files(), []) - - write_into_file_at_path("added_file", "new stuff") - self.scm.add("added_file") - - os.mkdir("added_dir") - write_into_file_at_path("added_dir/added_file2", "new stuff") - self.scm.add("added_dir") - - # SVN reports directory changes, Git does not. - added_files = self.scm.added_files() - if "added_dir" in added_files: - added_files.remove("added_dir") - self.assertEqual(added_files, ["added_dir/added_file2", "added_file"]) - - # Test also to make sure clean_working_directory removes added files - self.scm.clean_working_directory() - self.assertEqual(self.scm.added_files(), []) - self.assertFalse(os.path.exists("added_file")) - self.assertFalse(os.path.exists("added_dir")) - - def _shared_test_changed_files_for_revision(self): - # SVN reports directory changes, Git does not. - changed_files = self.scm.changed_files_for_revision(3) - if "test_dir" in changed_files: - changed_files.remove("test_dir") - self.assertEqual(changed_files, ["test_dir/test_file3", "test_file"]) - self.assertEqual(sorted(self.scm.changed_files_for_revision(4)), sorted(["test_file", "test_file2"])) # Git and SVN return different orders. - self.assertEqual(self.scm.changed_files_for_revision(2), ["test_file"]) - - def _shared_test_contents_at_revision(self): - self.assertEqual(self.scm.contents_at_revision("test_file", 3), "test1test2") - self.assertEqual(self.scm.contents_at_revision("test_file", 4), "test1test2test3\n") - - # Verify that contents_at_revision returns a byte array, aka str(): - self.assertEqual(self.scm.contents_at_revision("test_file", 5), u"latin1 test: \u00A0\n".encode("latin1")) - self.assertEqual(self.scm.contents_at_revision("test_file2", 5), u"utf-8 test: \u00A0\n".encode("utf-8")) - - self.assertEqual(self.scm.contents_at_revision("test_file2", 4), "second file") - # Files which don't exist: - # Currently we raise instead of returning None because detecting the difference between - # "file not found" and any other error seems impossible with svn (git seems to expose such through the return code). - self.assertRaises(ScriptError, self.scm.contents_at_revision, "test_file2", 2) - self.assertRaises(ScriptError, self.scm.contents_at_revision, "does_not_exist", 2) - - def _shared_test_revisions_changing_file(self): - self.assertEqual(self.scm.revisions_changing_file("test_file"), [5, 4, 3, 2]) - self.assertRaises(ScriptError, self.scm.revisions_changing_file, "non_existent_file") - - def _shared_test_committer_email_for_revision(self): - self.assertEqual(self.scm.committer_email_for_revision(3), getpass.getuser()) # Committer "email" will be the current user - - def _shared_test_reverse_diff(self): - self._setup_webkittools_scripts_symlink(self.scm) # Git's apply_reverse_diff uses resolve-ChangeLogs - # Only test the simple case, as any other will end up with conflict markers. - self.scm.apply_reverse_diff('5') - self.assertEqual(read_from_path('test_file'), "test1test2test3\n") - - def _shared_test_diff_for_revision(self): - # Patch formats are slightly different between svn and git, so just regexp for things we know should be there. - r3_patch = self.scm.diff_for_revision(4) - self.assertTrue(re.search('test3', r3_patch)) - self.assertFalse(re.search('test4', r3_patch)) - self.assertTrue(re.search('test2', r3_patch)) - self.assertTrue(re.search('test2', self.scm.diff_for_revision(3))) - - def _shared_test_svn_apply_git_patch(self): - self._setup_webkittools_scripts_symlink(self.scm) - git_binary_addition = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif -new file mode 100644 -index 0000000000000000000000000000000000000000..64a9532e7794fcd791f6f12157406d90 -60151690 -GIT binary patch -literal 512 -zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c? -zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap -zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ -zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A -zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&) -zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b -zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB -z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X -z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4 -ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H - -literal 0 -HcmV?d00001 - -""" - self.checkout.apply_patch(self._create_patch(git_binary_addition)) - added = read_from_path('fizzbuzz7.gif', encoding=None) - self.assertEqual(512, len(added)) - self.assertTrue(added.startswith('GIF89a')) - self.assertTrue('fizzbuzz7.gif' in self.scm.changed_files()) - - # The file already exists. - self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_addition)) - - git_binary_modification = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif -index 64a9532e7794fcd791f6f12157406d9060151690..323fae03f4606ea9991df8befbb2fca7 -GIT binary patch -literal 7 -OcmYex&reD$;sO8*F9L)B - -literal 512 -zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c? -zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap -zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ -zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A -zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&) -zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b -zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB -z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X -z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4 -ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H - -""" - self.checkout.apply_patch(self._create_patch(git_binary_modification)) - modified = read_from_path('fizzbuzz7.gif', encoding=None) - self.assertEqual('foobar\n', modified) - self.assertTrue('fizzbuzz7.gif' in self.scm.changed_files()) - - # Applying the same modification should fail. - self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_modification)) - - git_binary_deletion = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif -deleted file mode 100644 -index 323fae0..0000000 -GIT binary patch -literal 0 -HcmV?d00001 - -literal 7 -OcmYex&reD$;sO8*F9L)B - -""" - self.checkout.apply_patch(self._create_patch(git_binary_deletion)) - self.assertFalse(os.path.exists('fizzbuzz7.gif')) - self.assertFalse('fizzbuzz7.gif' in self.scm.changed_files()) - - # Cannot delete again. - self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_deletion)) - - def _shared_test_add_recursively(self): - os.mkdir("added_dir") - write_into_file_at_path("added_dir/added_file", "new stuff") - self.scm.add("added_dir/added_file") - self.assertTrue("added_dir/added_file" in self.scm.added_files()) - -class SVNTest(SCMTest): - - @staticmethod - def _set_date_and_reviewer(changelog_entry): - # Joe Cool matches the reviewer set in SCMTest._create_patch - changelog_entry = changelog_entry.replace('REVIEWER_HERE', 'Joe Cool') - # svn-apply will update ChangeLog entries with today's date. - return changelog_entry.replace('DATE_HERE', date.today().isoformat()) - - def test_svn_apply(self): - first_entry = """2009-10-26 Eric Seidel <eric@webkit.org> - - Reviewed by Foo Bar. - - Most awesome change ever. - - * scm_unittest.py: -""" - intermediate_entry = """2009-10-27 Eric Seidel <eric@webkit.org> - - Reviewed by Baz Bar. - - A more awesomer change yet! - - * scm_unittest.py: -""" - one_line_overlap_patch = """Index: ChangeLog -=================================================================== ---- ChangeLog (revision 5) -+++ ChangeLog (working copy) -@@ -1,5 +1,13 @@ - 2009-10-26 Eric Seidel <eric@webkit.org> - -+ Reviewed by NOBODY (OOPS!). -+ -+ Second most awesome change ever. -+ -+ * scm_unittest.py: -+ -+2009-10-26 Eric Seidel <eric@webkit.org> -+ - Reviewed by Foo Bar. - - Most awesome change ever. -""" - one_line_overlap_entry = """DATE_HERE Eric Seidel <eric@webkit.org> - - Reviewed by REVIEWER_HERE. - - Second most awesome change ever. - - * scm_unittest.py: -""" - two_line_overlap_patch = """Index: ChangeLog -=================================================================== ---- ChangeLog (revision 5) -+++ ChangeLog (working copy) -@@ -2,6 +2,14 @@ - - Reviewed by Foo Bar. - -+ Second most awesome change ever. -+ -+ * scm_unittest.py: -+ -+2009-10-26 Eric Seidel <eric@webkit.org> -+ -+ Reviewed by Foo Bar. -+ - Most awesome change ever. - - * scm_unittest.py: -""" - two_line_overlap_entry = """DATE_HERE Eric Seidel <eric@webkit.org> - - Reviewed by Foo Bar. - - Second most awesome change ever. - - * scm_unittest.py: -""" - write_into_file_at_path('ChangeLog', first_entry) - run_command(['svn', 'add', 'ChangeLog']) - run_command(['svn', 'commit', '--quiet', '--message', 'ChangeLog commit']) - - # Patch files were created against just 'first_entry'. - # Add a second commit to make svn-apply have to apply the patches with fuzz. - changelog_contents = "%s\n%s" % (intermediate_entry, first_entry) - write_into_file_at_path('ChangeLog', changelog_contents) - run_command(['svn', 'commit', '--quiet', '--message', 'Intermediate commit']) - - self._setup_webkittools_scripts_symlink(self.scm) - self.checkout.apply_patch(self._create_patch(one_line_overlap_patch)) - expected_changelog_contents = "%s\n%s" % (self._set_date_and_reviewer(one_line_overlap_entry), changelog_contents) - self.assertEquals(read_from_path('ChangeLog'), expected_changelog_contents) - - self.scm.revert_files(['ChangeLog']) - self.checkout.apply_patch(self._create_patch(two_line_overlap_patch)) - expected_changelog_contents = "%s\n%s" % (self._set_date_and_reviewer(two_line_overlap_entry), changelog_contents) - self.assertEquals(read_from_path('ChangeLog'), expected_changelog_contents) - - def setUp(self): - SVNTestRepository.setup(self) - os.chdir(self.svn_checkout_path) - self.scm = detect_scm_system(self.svn_checkout_path) - # For historical reasons, we test some checkout code here too. - self.checkout = Checkout(self.scm) - - def tearDown(self): - SVNTestRepository.tear_down(self) - - def test_detect_scm_system_relative_url(self): - scm = detect_scm_system(".") - # I wanted to assert that we got the right path, but there was some - # crazy magic with temp folder names that I couldn't figure out. - self.assertTrue(scm.checkout_root) - - def test_create_patch_is_full_patch(self): - test_dir_path = os.path.join(self.svn_checkout_path, "test_dir2") - os.mkdir(test_dir_path) - test_file_path = os.path.join(test_dir_path, 'test_file2') - write_into_file_at_path(test_file_path, 'test content') - run_command(['svn', 'add', 'test_dir2']) - - # create_patch depends on 'svn-create-patch', so make a dummy version. - scripts_path = os.path.join(self.svn_checkout_path, 'WebKitTools', 'Scripts') - os.makedirs(scripts_path) - create_patch_path = os.path.join(scripts_path, 'svn-create-patch') - write_into_file_at_path(create_patch_path, '#!/bin/sh\necho $PWD') # We could pass -n to prevent the \n, but not all echo accept -n. - os.chmod(create_patch_path, stat.S_IXUSR | stat.S_IRUSR) - - # Change into our test directory and run the create_patch command. - os.chdir(test_dir_path) - scm = detect_scm_system(test_dir_path) - self.assertEqual(scm.checkout_root, self.svn_checkout_path) # Sanity check that detection worked right. - patch_contents = scm.create_patch() - # Our fake 'svn-create-patch' returns $PWD instead of a patch, check that it was executed from the root of the repo. - self.assertEqual("%s\n" % os.path.realpath(scm.checkout_root), patch_contents) # Add a \n because echo adds a \n. - - def test_detection(self): - scm = detect_scm_system(self.svn_checkout_path) - self.assertEqual(scm.display_name(), "svn") - self.assertEqual(scm.supports_local_commits(), False) - - def test_apply_small_binary_patch(self): - patch_contents = """Index: test_file.swf -=================================================================== -Cannot display: file marked as a binary type. -svn:mime-type = application/octet-stream - -Property changes on: test_file.swf -___________________________________________________________________ -Name: svn:mime-type - + application/octet-stream - - -Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== -""" - expected_contents = base64.b64decode("Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==") - self._setup_webkittools_scripts_symlink(self.scm) - patch_file = self._create_patch(patch_contents) - self.checkout.apply_patch(patch_file) - actual_contents = read_from_path("test_file.swf", encoding=None) - self.assertEqual(actual_contents, expected_contents) - - def test_apply_svn_patch(self): - scm = detect_scm_system(self.svn_checkout_path) - patch = self._create_patch(_svn_diff("-r5:4")) - self._setup_webkittools_scripts_symlink(scm) - Checkout(scm).apply_patch(patch) - - def test_apply_svn_patch_force(self): - scm = detect_scm_system(self.svn_checkout_path) - patch = self._create_patch(_svn_diff("-r3:5")) - self._setup_webkittools_scripts_symlink(scm) - self.assertRaises(ScriptError, Checkout(scm).apply_patch, patch, force=True) - - def test_commit_logs(self): - # Commits have dates and usernames in them, so we can't just direct compare. - self.assertTrue(re.search('fourth commit', self.scm.last_svn_commit_log())) - self.assertTrue(re.search('second commit', self.scm.svn_commit_log(3))) - - def _shared_test_commit_with_message(self, username=None): - write_into_file_at_path('test_file', 'more test content') - commit_text = self.scm.commit_with_message("another test commit", username) - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - - self.scm.dryrun = True - write_into_file_at_path('test_file', 'still more test content') - commit_text = self.scm.commit_with_message("yet another test commit", username) - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '0') - - def test_commit_text_parsing(self): - self._shared_test_commit_with_message() - - def test_commit_with_username(self): - self._shared_test_commit_with_message("dbates@webkit.org") - - def test_commit_without_authorization(self): - self.scm.has_authorization_for_realm = lambda: False - self.assertRaises(AuthenticationError, self._shared_test_commit_with_message) - - def test_has_authorization_for_realm(self): - scm = detect_scm_system(self.svn_checkout_path) - fake_home_dir = tempfile.mkdtemp(suffix="fake_home_dir") - svn_config_dir_path = os.path.join(fake_home_dir, ".subversion") - os.mkdir(svn_config_dir_path) - fake_webkit_auth_file = os.path.join(svn_config_dir_path, "fake_webkit_auth_file") - write_into_file_at_path(fake_webkit_auth_file, SVN.svn_server_realm) - self.assertTrue(scm.has_authorization_for_realm(home_directory=fake_home_dir)) - os.remove(fake_webkit_auth_file) - os.rmdir(svn_config_dir_path) - os.rmdir(fake_home_dir) - - def test_not_have_authorization_for_realm(self): - scm = detect_scm_system(self.svn_checkout_path) - fake_home_dir = tempfile.mkdtemp(suffix="fake_home_dir") - svn_config_dir_path = os.path.join(fake_home_dir, ".subversion") - os.mkdir(svn_config_dir_path) - self.assertFalse(scm.has_authorization_for_realm(home_directory=fake_home_dir)) - os.rmdir(svn_config_dir_path) - os.rmdir(fake_home_dir) - - def test_reverse_diff(self): - self._shared_test_reverse_diff() - - def test_diff_for_revision(self): - self._shared_test_diff_for_revision() - - def test_svn_apply_git_patch(self): - self._shared_test_svn_apply_git_patch() - - def test_changed_files(self): - self._shared_test_changed_files() - - def test_changed_files_for_revision(self): - self._shared_test_changed_files_for_revision() - - def test_added_files(self): - self._shared_test_added_files() - - def test_contents_at_revision(self): - self._shared_test_contents_at_revision() - - def test_revisions_changing_file(self): - self._shared_test_revisions_changing_file() - - def test_committer_email_for_revision(self): - self._shared_test_committer_email_for_revision() - - def test_add_recursively(self): - self._shared_test_add_recursively() - - def test_delete(self): - os.chdir(self.svn_checkout_path) - self.scm.delete("test_file") - self.assertTrue("test_file" in self.scm.deleted_files()) - - def test_propset_propget(self): - filepath = os.path.join(self.svn_checkout_path, "test_file") - expected_mime_type = "x-application/foo-bar" - self.scm.propset("svn:mime-type", expected_mime_type, filepath) - self.assertEqual(expected_mime_type, self.scm.propget("svn:mime-type", filepath)) - - def test_show_head(self): - write_into_file_at_path("test_file", u"Hello!", "utf-8") - SVNTestRepository._svn_commit("fourth commit") - self.assertEqual("Hello!", self.scm.show_head('test_file')) - - def test_show_head_binary(self): - data = "\244" - write_into_file_at_path("binary_file", data, encoding=None) - self.scm.add("binary_file") - self.scm.commit_with_message("a test commit") - self.assertEqual(data, self.scm.show_head('binary_file')) - - def do_test_diff_for_file(self): - write_into_file_at_path('test_file', 'some content') - self.scm.commit_with_message("a test commit") - diff = self.scm.diff_for_file('test_file') - self.assertEqual(diff, "") - - write_into_file_at_path("test_file", "changed content") - diff = self.scm.diff_for_file('test_file') - self.assertTrue("-some content" in diff) - self.assertTrue("+changed content" in diff) - - def clean_bogus_dir(self): - self.bogus_dir = self.scm._bogus_dir_name() - if os.path.exists(self.bogus_dir): - shutil.rmtree(self.bogus_dir) - - def test_diff_for_file_with_existing_bogus_dir(self): - self.clean_bogus_dir() - os.mkdir(self.bogus_dir) - self.do_test_diff_for_file() - self.assertTrue(os.path.exists(self.bogus_dir)) - shutil.rmtree(self.bogus_dir) - - def test_diff_for_file_with_missing_bogus_dir(self): - self.clean_bogus_dir() - self.do_test_diff_for_file() - self.assertFalse(os.path.exists(self.bogus_dir)) - - def test_svn_lock(self): - svn_root_lock_path = ".svn/lock" - write_into_file_at_path(svn_root_lock_path, "", "utf-8") - # webkit-patch uses a Checkout object and runs update-webkit, just use svn update here. - self.assertRaises(ScriptError, run_command, ['svn', 'update']) - self.scm.clean_working_directory() - self.assertFalse(os.path.exists(svn_root_lock_path)) - run_command(['svn', 'update']) # Should succeed and not raise. - - -class GitTest(SCMTest): - - def setUp(self): - """Sets up fresh git repository with one commit. Then setups a second git - repo that tracks the first one.""" - self.original_dir = os.getcwd() - - self.untracking_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout2") - run_command(['git', 'init', self.untracking_checkout_path]) - - os.chdir(self.untracking_checkout_path) - write_into_file_at_path('foo_file', 'foo') - run_command(['git', 'add', 'foo_file']) - run_command(['git', 'commit', '-am', 'dummy commit']) - self.untracking_scm = detect_scm_system(self.untracking_checkout_path) - - self.tracking_git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout") - run_command(['git', 'clone', '--quiet', self.untracking_checkout_path, self.tracking_git_checkout_path]) - os.chdir(self.tracking_git_checkout_path) - self.tracking_scm = detect_scm_system(self.tracking_git_checkout_path) - - def tearDown(self): - # Change back to a valid directory so that later calls to os.getcwd() do not fail. - os.chdir(self.original_dir) - run_command(['rm', '-rf', self.tracking_git_checkout_path]) - run_command(['rm', '-rf', self.untracking_checkout_path]) - - def test_remote_branch_ref(self): - self.assertEqual(self.tracking_scm.remote_branch_ref(), 'refs/remotes/origin/master') - - os.chdir(self.untracking_checkout_path) - self.assertRaises(ScriptError, self.untracking_scm.remote_branch_ref) - - def test_multiple_remotes(self): - run_command(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote1']) - run_command(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote2']) - self.assertEqual(self.tracking_scm.remote_branch_ref(), 'remote1') - -class GitSVNTest(SCMTest): - - def _setup_git_checkout(self): - self.git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout") - # --quiet doesn't make git svn silent, so we use run_silent to redirect output - run_silent(['git', 'svn', 'clone', '-T', 'trunk', self.svn_repo_url, self.git_checkout_path]) - os.chdir(self.git_checkout_path) - - def _tear_down_git_checkout(self): - # Change back to a valid directory so that later calls to os.getcwd() do not fail. - os.chdir(self.original_dir) - run_command(['rm', '-rf', self.git_checkout_path]) - - def setUp(self): - self.original_dir = os.getcwd() - - SVNTestRepository.setup(self) - self._setup_git_checkout() - self.scm = detect_scm_system(self.git_checkout_path) - # For historical reasons, we test some checkout code here too. - self.checkout = Checkout(self.scm) - - def tearDown(self): - SVNTestRepository.tear_down(self) - self._tear_down_git_checkout() - - def test_detection(self): - scm = detect_scm_system(self.git_checkout_path) - self.assertEqual(scm.display_name(), "git") - self.assertEqual(scm.supports_local_commits(), True) - - def test_read_git_config(self): - key = 'test.git-config' - value = 'git-config value' - run_command(['git', 'config', key, value]) - self.assertEqual(self.scm.read_git_config(key), value) - - def test_local_commits(self): - test_file = os.path.join(self.git_checkout_path, 'test_file') - write_into_file_at_path(test_file, 'foo') - run_command(['git', 'commit', '-a', '-m', 'local commit']) - - self.assertEqual(len(self.scm.local_commits()), 1) - - def test_discard_local_commits(self): - test_file = os.path.join(self.git_checkout_path, 'test_file') - write_into_file_at_path(test_file, 'foo') - run_command(['git', 'commit', '-a', '-m', 'local commit']) - - self.assertEqual(len(self.scm.local_commits()), 1) - self.scm.discard_local_commits() - self.assertEqual(len(self.scm.local_commits()), 0) - - def test_delete_branch(self): - new_branch = 'foo' - - run_command(['git', 'checkout', '-b', new_branch]) - self.assertEqual(run_command(['git', 'symbolic-ref', 'HEAD']).strip(), 'refs/heads/' + new_branch) - - run_command(['git', 'checkout', '-b', 'bar']) - self.scm.delete_branch(new_branch) - - self.assertFalse(re.search(r'foo', run_command(['git', 'branch']))) - - def test_remote_merge_base(self): - # Diff to merge-base should include working-copy changes, - # which the diff to svn_branch.. doesn't. - test_file = os.path.join(self.git_checkout_path, 'test_file') - write_into_file_at_path(test_file, 'foo') - - diff_to_common_base = _git_diff(self.scm.remote_branch_ref() + '..') - diff_to_merge_base = _git_diff(self.scm.remote_merge_base()) - - self.assertFalse(re.search(r'foo', diff_to_common_base)) - self.assertTrue(re.search(r'foo', diff_to_merge_base)) - - def test_rebase_in_progress(self): - svn_test_file = os.path.join(self.svn_checkout_path, 'test_file') - write_into_file_at_path(svn_test_file, "svn_checkout") - run_command(['svn', 'commit', '--message', 'commit to conflict with git commit'], cwd=self.svn_checkout_path) - - git_test_file = os.path.join(self.git_checkout_path, 'test_file') - write_into_file_at_path(git_test_file, "git_checkout") - run_command(['git', 'commit', '-a', '-m', 'commit to be thrown away by rebase abort']) - - # --quiet doesn't make git svn silent, so use run_silent to redirect output - self.assertRaises(ScriptError, run_silent, ['git', 'svn', '--quiet', 'rebase']) # Will fail due to a conflict leaving us mid-rebase. - - scm = detect_scm_system(self.git_checkout_path) - self.assertTrue(scm.rebase_in_progress()) - - # Make sure our cleanup works. - scm.clean_working_directory() - self.assertFalse(scm.rebase_in_progress()) - - # Make sure cleanup doesn't throw when no rebase is in progress. - scm.clean_working_directory() - - def test_commitish_parsing(self): - scm = detect_scm_system(self.git_checkout_path) - - # Multiple revisions are cherry-picked. - self.assertEqual(len(scm.commit_ids_from_commitish_arguments(['HEAD~2'])), 1) - self.assertEqual(len(scm.commit_ids_from_commitish_arguments(['HEAD', 'HEAD~2'])), 2) - - # ... is an invalid range specifier - self.assertRaises(ScriptError, scm.commit_ids_from_commitish_arguments, ['trunk...HEAD']) - - def test_commitish_order(self): - scm = detect_scm_system(self.git_checkout_path) - - commit_range = 'HEAD~3..HEAD' - - actual_commits = scm.commit_ids_from_commitish_arguments([commit_range]) - expected_commits = [] - expected_commits += reversed(run_command(['git', 'rev-list', commit_range]).splitlines()) - - self.assertEqual(actual_commits, expected_commits) - - def test_apply_git_patch(self): - scm = detect_scm_system(self.git_checkout_path) - # We carefullly pick a diff which does not have a directory addition - # as currently svn-apply will error out when trying to remove directories - # in Git: https://bugs.webkit.org/show_bug.cgi?id=34871 - patch = self._create_patch(_git_diff('HEAD..HEAD^')) - self._setup_webkittools_scripts_symlink(scm) - Checkout(scm).apply_patch(patch) - - def test_apply_git_patch_force(self): - scm = detect_scm_system(self.git_checkout_path) - patch = self._create_patch(_git_diff('HEAD~2..HEAD')) - self._setup_webkittools_scripts_symlink(scm) - self.assertRaises(ScriptError, Checkout(scm).apply_patch, patch, force=True) - - def test_commit_text_parsing(self): - write_into_file_at_path('test_file', 'more test content') - commit_text = self.scm.commit_with_message("another test commit") - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - - self.scm.dryrun = True - write_into_file_at_path('test_file', 'still more test content') - commit_text = self.scm.commit_with_message("yet another test commit") - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '0') - - def test_commit_with_message_working_copy_only(self): - write_into_file_at_path('test_file_commit1', 'more test content') - run_command(['git', 'add', 'test_file_commit1']) - scm = detect_scm_system(self.git_checkout_path) - commit_text = scm.commit_with_message("yet another test commit") - - self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertTrue(re.search(r'test_file_commit1', svn_log)) - - def _one_local_commit(self): - write_into_file_at_path('test_file_commit1', 'more test content') - run_command(['git', 'add', 'test_file_commit1']) - self.scm.commit_locally_with_message("another test commit") - - def _one_local_commit_plus_working_copy_changes(self): - self._one_local_commit() - write_into_file_at_path('test_file_commit2', 'still more test content') - run_command(['git', 'add', 'test_file_commit2']) - - def _two_local_commits(self): - self._one_local_commit() - write_into_file_at_path('test_file_commit2', 'still more test content') - run_command(['git', 'add', 'test_file_commit2']) - self.scm.commit_locally_with_message("yet another test commit") - - def _three_local_commits(self): - write_into_file_at_path('test_file_commit0', 'more test content') - run_command(['git', 'add', 'test_file_commit0']) - self.scm.commit_locally_with_message("another test commit") - self._two_local_commits() - - def test_revisions_changing_files_with_local_commit(self): - self._one_local_commit() - self.assertEquals(self.scm.revisions_changing_file('test_file_commit1'), []) - - def test_commit_with_message(self): - self._one_local_commit_plus_working_copy_changes() - scm = detect_scm_system(self.git_checkout_path) - self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "yet another test commit") - commit_text = scm.commit_with_message("yet another test commit", force_squash=True) - - self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertTrue(re.search(r'test_file_commit2', svn_log)) - self.assertTrue(re.search(r'test_file_commit1', svn_log)) - - def test_commit_with_message_git_commit(self): - self._two_local_commits() - - scm = detect_scm_system(self.git_checkout_path) - commit_text = scm.commit_with_message("another test commit", git_commit="HEAD^") - self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') - - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertTrue(re.search(r'test_file_commit1', svn_log)) - self.assertFalse(re.search(r'test_file_commit2', svn_log)) - - def test_commit_with_message_git_commit_range(self): - self._three_local_commits() - - scm = detect_scm_system(self.git_checkout_path) - commit_text = scm.commit_with_message("another test commit", git_commit="HEAD~2..HEAD") - self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') - - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertFalse(re.search(r'test_file_commit0', svn_log)) - self.assertTrue(re.search(r'test_file_commit1', svn_log)) - self.assertTrue(re.search(r'test_file_commit2', svn_log)) - - def test_changed_files_working_copy_only(self): - self._one_local_commit_plus_working_copy_changes() - scm = detect_scm_system(self.git_checkout_path) - commit_text = scm.commit_with_message("another test commit", git_commit="HEAD..") - self.assertFalse(re.search(r'test_file_commit1', svn_log)) - self.assertTrue(re.search(r'test_file_commit2', svn_log)) - - def test_commit_with_message_only_local_commit(self): - self._one_local_commit() - scm = detect_scm_system(self.git_checkout_path) - commit_text = scm.commit_with_message("another test commit") - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertTrue(re.search(r'test_file_commit1', svn_log)) - - def test_commit_with_message_multiple_local_commits_and_working_copy(self): - self._two_local_commits() - write_into_file_at_path('test_file_commit1', 'working copy change') - scm = detect_scm_system(self.git_checkout_path) - - self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "another test commit") - commit_text = scm.commit_with_message("another test commit", force_squash=True) - - self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertTrue(re.search(r'test_file_commit2', svn_log)) - self.assertTrue(re.search(r'test_file_commit1', svn_log)) - - def test_commit_with_message_git_commit_and_working_copy(self): - self._two_local_commits() - write_into_file_at_path('test_file_commit1', 'working copy change') - scm = detect_scm_system(self.git_checkout_path) - self.assertRaises(ScriptError, scm.commit_with_message, "another test commit", git_commit="HEAD^") - - def test_commit_with_message_multiple_local_commits_always_squash(self): - self._two_local_commits() - scm = detect_scm_system(self.git_checkout_path) - scm._assert_can_squash = lambda working_directory_is_clean: True - commit_text = scm.commit_with_message("yet another test commit") - self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') - - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertTrue(re.search(r'test_file_commit2', svn_log)) - self.assertTrue(re.search(r'test_file_commit1', svn_log)) - - def test_commit_with_message_multiple_local_commits(self): - self._two_local_commits() - scm = detect_scm_system(self.git_checkout_path) - self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "yet another test commit") - commit_text = scm.commit_with_message("yet another test commit", force_squash=True) - - self.assertEqual(scm.svn_revision_from_commit_text(commit_text), '6') - - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertTrue(re.search(r'test_file_commit2', svn_log)) - self.assertTrue(re.search(r'test_file_commit1', svn_log)) - - def test_commit_with_message_not_synced(self): - run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) - self._two_local_commits() - scm = detect_scm_system(self.git_checkout_path) - self.assertRaises(AmbiguousCommitError, scm.commit_with_message, "another test commit") - self.assertRaises(ScriptError, scm.commit_with_message, "another test commit", force_squash=True) - - def test_remote_branch_ref(self): - self.assertEqual(self.scm.remote_branch_ref(), 'refs/remotes/trunk') - - def test_reverse_diff(self): - self._shared_test_reverse_diff() - - def test_diff_for_revision(self): - self._shared_test_diff_for_revision() - - def test_svn_apply_git_patch(self): - self._shared_test_svn_apply_git_patch() - - def test_create_patch_local_plus_working_copy(self): - self._one_local_commit_plus_working_copy_changes() - scm = detect_scm_system(self.git_checkout_path) - patch = scm.create_patch() - self.assertTrue(re.search(r'test_file_commit1', patch)) - self.assertTrue(re.search(r'test_file_commit2', patch)) - - def test_create_patch(self): - self._one_local_commit_plus_working_copy_changes() - scm = detect_scm_system(self.git_checkout_path) - patch = scm.create_patch() - self.assertTrue(re.search(r'test_file_commit2', patch)) - self.assertTrue(re.search(r'test_file_commit1', patch)) - - def test_create_patch_with_changed_files(self): - self._one_local_commit_plus_working_copy_changes() - scm = detect_scm_system(self.git_checkout_path) - patch = scm.create_patch(changed_files=['test_file_commit2']) - self.assertTrue(re.search(r'test_file_commit2', patch)) - - def test_create_patch_with_rm_and_changed_files(self): - self._one_local_commit_plus_working_copy_changes() - scm = detect_scm_system(self.git_checkout_path) - os.remove('test_file_commit1') - patch = scm.create_patch() - patch_with_changed_files = scm.create_patch(changed_files=['test_file_commit1', 'test_file_commit2']) - self.assertEquals(patch, patch_with_changed_files) - - def test_create_patch_git_commit(self): - self._two_local_commits() - scm = detect_scm_system(self.git_checkout_path) - patch = scm.create_patch(git_commit="HEAD^") - self.assertTrue(re.search(r'test_file_commit1', patch)) - self.assertFalse(re.search(r'test_file_commit2', patch)) - - def test_create_patch_git_commit_range(self): - self._three_local_commits() - scm = detect_scm_system(self.git_checkout_path) - patch = scm.create_patch(git_commit="HEAD~2..HEAD") - self.assertFalse(re.search(r'test_file_commit0', patch)) - self.assertTrue(re.search(r'test_file_commit2', patch)) - self.assertTrue(re.search(r'test_file_commit1', patch)) - - def test_create_patch_working_copy_only(self): - self._one_local_commit_plus_working_copy_changes() - scm = detect_scm_system(self.git_checkout_path) - patch = scm.create_patch(git_commit="HEAD..") - self.assertFalse(re.search(r'test_file_commit1', patch)) - self.assertTrue(re.search(r'test_file_commit2', patch)) - - def test_create_patch_multiple_local_commits(self): - self._two_local_commits() - scm = detect_scm_system(self.git_checkout_path) - patch = scm.create_patch() - self.assertTrue(re.search(r'test_file_commit2', patch)) - self.assertTrue(re.search(r'test_file_commit1', patch)) - - def test_create_patch_not_synced(self): - run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) - self._two_local_commits() - scm = detect_scm_system(self.git_checkout_path) - self.assertRaises(ScriptError, scm.create_patch) - - def test_create_binary_patch(self): - # Create a git binary patch and check the contents. - scm = detect_scm_system(self.git_checkout_path) - test_file_name = 'binary_file' - test_file_path = os.path.join(self.git_checkout_path, test_file_name) - file_contents = ''.join(map(chr, range(256))) - write_into_file_at_path(test_file_path, file_contents, encoding=None) - run_command(['git', 'add', test_file_name]) - patch = scm.create_patch() - self.assertTrue(re.search(r'\nliteral 0\n', patch)) - self.assertTrue(re.search(r'\nliteral 256\n', patch)) - - # Check if we can apply the created patch. - run_command(['git', 'rm', '-f', test_file_name]) - self._setup_webkittools_scripts_symlink(scm) - self.checkout.apply_patch(self._create_patch(patch)) - self.assertEqual(file_contents, read_from_path(test_file_path, encoding=None)) - - # Check if we can create a patch from a local commit. - write_into_file_at_path(test_file_path, file_contents, encoding=None) - run_command(['git', 'add', test_file_name]) - run_command(['git', 'commit', '-m', 'binary diff']) - patch_from_local_commit = scm.create_patch('HEAD') - self.assertTrue(re.search(r'\nliteral 0\n', patch_from_local_commit)) - self.assertTrue(re.search(r'\nliteral 256\n', patch_from_local_commit)) - - def test_changed_files_local_plus_working_copy(self): - self._one_local_commit_plus_working_copy_changes() - scm = detect_scm_system(self.git_checkout_path) - files = scm.changed_files() - self.assertTrue('test_file_commit1' in files) - self.assertTrue('test_file_commit2' in files) - - def test_changed_files_git_commit(self): - self._two_local_commits() - scm = detect_scm_system(self.git_checkout_path) - files = scm.changed_files(git_commit="HEAD^") - self.assertTrue('test_file_commit1' in files) - self.assertFalse('test_file_commit2' in files) - - def test_changed_files_git_commit_range(self): - self._three_local_commits() - scm = detect_scm_system(self.git_checkout_path) - files = scm.changed_files(git_commit="HEAD~2..HEAD") - self.assertTrue('test_file_commit0' not in files) - self.assertTrue('test_file_commit1' in files) - self.assertTrue('test_file_commit2' in files) - - def test_changed_files_working_copy_only(self): - self._one_local_commit_plus_working_copy_changes() - scm = detect_scm_system(self.git_checkout_path) - files = scm.changed_files(git_commit="HEAD..") - self.assertFalse('test_file_commit1' in files) - self.assertTrue('test_file_commit2' in files) - - def test_changed_files_multiple_local_commits(self): - self._two_local_commits() - scm = detect_scm_system(self.git_checkout_path) - files = scm.changed_files() - self.assertTrue('test_file_commit2' in files) - self.assertTrue('test_file_commit1' in files) - - def test_changed_files_not_synced(self): - run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) - self._two_local_commits() - scm = detect_scm_system(self.git_checkout_path) - self.assertRaises(ScriptError, scm.changed_files) - - def test_changed_files(self): - self._shared_test_changed_files() - - def test_changed_files_for_revision(self): - self._shared_test_changed_files_for_revision() - - def test_contents_at_revision(self): - self._shared_test_contents_at_revision() - - def test_revisions_changing_file(self): - self._shared_test_revisions_changing_file() - - def test_added_files(self): - self._shared_test_added_files() - - def test_committer_email_for_revision(self): - self._shared_test_committer_email_for_revision() - - def test_add_recursively(self): - self._shared_test_add_recursively() - - def test_delete(self): - self._two_local_commits() - self.scm.delete('test_file_commit1') - self.assertTrue("test_file_commit1" in self.scm.deleted_files()) - - def test_to_object_name(self): - relpath = 'test_file_commit1' - fullpath = os.path.join(self.git_checkout_path, relpath) - self._two_local_commits() - self.assertEqual(relpath, self.scm.to_object_name(fullpath)) - - def test_show_head(self): - self._two_local_commits() - self.assertEqual("more test content", self.scm.show_head('test_file_commit1')) - - def test_show_head_binary(self): - self._two_local_commits() - data = "\244" - write_into_file_at_path("binary_file", data, encoding=None) - self.scm.add("binary_file") - self.scm.commit_locally_with_message("a test commit") - self.assertEqual(data, self.scm.show_head('binary_file')) - - def test_diff_for_file(self): - self._two_local_commits() - write_into_file_at_path('test_file_commit1', "Updated", encoding=None) - - diff = self.scm.diff_for_file('test_file_commit1') - cached_diff = self.scm.diff_for_file('test_file_commit1') - self.assertTrue("+Updated" in diff) - self.assertTrue("-more test content" in diff) - - self.scm.add('test_file_commit1') - - cached_diff = self.scm.diff_for_file('test_file_commit1') - self.assertTrue("+Updated" in cached_diff) - self.assertTrue("-more test content" in cached_diff) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/config/__init__.py b/WebKitTools/Scripts/webkitpy/common/config/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/common/config/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/common/config/build.py b/WebKitTools/Scripts/webkitpy/common/config/build.py deleted file mode 100644 index c45f122..0000000 --- a/WebKitTools/Scripts/webkitpy/common/config/build.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (C) 2010 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Functions relating to building WebKit""" - -import re - - -def _should_file_trigger_build(target_platform, file): - # The directories and patterns lists below map directory names or - # regexp patterns to the bot platforms for which they should trigger a - # build. Mapping to the empty list means that no builds should be - # triggered on any platforms. Earlier directories/patterns take - # precendence over later ones. - - # FIXME: The patterns below have only been verified to be correct on - # Windows. We should implement this for other platforms and start using - # it for their bots. Someone familiar with each platform will have to - # figure out what the right set of directories/patterns is for that - # platform. - assert(target_platform == "win") - - directories = [ - # Directories that shouldn't trigger builds on any bots. - ("BugsSite", []), - ("PageLoadTests", []), - ("PlanetWebKit", []), - ("WebCore/manual-tests", []), - ("WebKitExamplePlugins", []), - ("WebKitSite", []), - ("android", []), - ("brew", []), - ("efl", []), - ("haiku", []), - ("iphone", []), - ("opengl", []), - ("opentype", []), - ("openvg", []), - ("wx", []), - ("wince", []), - - # Directories that should trigger builds on only some bots. - ("JavaScriptGlue", ["mac"]), - ("LayoutTests/platform/mac", ["mac", "win"]), - ("LayoutTests/platform/mac-snowleopard", ["mac-snowleopard", "win"]), - ("WebCore/image-decoders", ["chromium"]), - ("cairo", ["gtk", "wincairo"]), - ("cf", ["chromium-mac", "mac", "qt", "win"]), - ("chromium", ["chromium"]), - ("cocoa", ["chromium-mac", "mac"]), - ("curl", ["gtk", "wincairo"]), - ("gobject", ["gtk"]), - ("gpu", ["chromium", "mac"]), - ("gstreamer", ["gtk"]), - ("gtk", ["gtk"]), - ("mac", ["chromium-mac", "mac"]), - ("mac-leopard", ["mac-leopard"]), - ("mac-snowleopard", ["mac-snowleopard"]), - ("mac-wk2", ["mac-snowleopard", "win"]), - ("objc", ["mac"]), - ("qt", ["qt"]), - ("skia", ["chromium"]), - ("soup", ["gtk"]), - ("v8", ["chromium"]), - ("win", ["chromium-win", "win"]), - ] - patterns = [ - # Patterns that shouldn't trigger builds on any bots. - (r"(?:^|/)Makefile$", []), - (r"/ARM", []), - (r"/CMake.*", []), - (r"/ChangeLog.*$", []), - (r"/LICENSE[^/]+$", []), - (r"ARM(?:v7)?\.(?:cpp|h)$", []), - (r"MIPS\.(?:cpp|h)$", []), - (r"WinCE\.(?:cpp|h|mm)$", []), - (r"\.(?:bkl|mk)$", []), - - # Patterns that should trigger builds on only some bots. - (r"/GNUmakefile\.am$", ["gtk"]), - (r"/\w+Chromium\w*\.(?:cpp|h|mm)$", ["chromium"]), - (r"Mac\.(?:cpp|h|mm)$", ["mac"]), - (r"\.exp$", ["mac"]), - (r"\.gypi?", ["chromium"]), - (r"\.order$", ["mac"]), - (r"\.pr[io]$", ["qt"]), - (r"\.xcconfig$", ["mac"]), - (r"\.xcodeproj/", ["mac"]), - ] - - base_platform = target_platform.split("-")[0] - - # See if the file is in one of the known directories. - for directory, platforms in directories: - if re.search(r"(?:^|/)%s/" % directory, file): - return target_platform in platforms or base_platform in platforms - - # See if the file matches a known pattern. - for pattern, platforms in patterns: - if re.search(pattern, file): - return target_platform in platforms or base_platform in platforms - - # See if the file is a platform-specific test result. - match = re.match("LayoutTests/platform/(?P<platform>[^/]+)/", file) - if match: - # See if the file is a test result for this platform, our base - # platform, or one of our sub-platforms. - return match.group("platform") in (target_platform, base_platform) or match.group("platform").startswith("%s-" % target_platform) - - # The file isn't one we know about specifically, so we should assume we - # have to build. - return True - - -def should_build(target_platform, changed_files): - """Returns true if the changed files affect the given platform, and - thus a build should be performed. target_platform should be one of the - platforms used in the build.webkit.org master's config.json file.""" - return any(_should_file_trigger_build(target_platform, file) for file in changed_files) diff --git a/WebKitTools/Scripts/webkitpy/common/config/build_unittest.py b/WebKitTools/Scripts/webkitpy/common/config/build_unittest.py deleted file mode 100644 index 3e70ff0..0000000 --- a/WebKitTools/Scripts/webkitpy/common/config/build_unittest.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright (C) 2010 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.config import build - - -class ShouldBuildTest(unittest.TestCase): - _should_build_tests = [ - (["BugsSite/foo", "WebCore/bar"], ["*"]), - (["BugsSite/foo"], []), - (["JavaScriptCore/JavaScriptCore.xcodeproj/foo"], ["mac-leopard", "mac-snowleopard"]), - (["JavaScriptGlue/foo", "WebCore/bar"], ["*"]), - (["JavaScriptGlue/foo"], ["mac-leopard", "mac-snowleopard"]), - (["LayoutTests/foo"], ["*"]), - (["LayoutTests/platform/chromium-linux/foo"], ["chromium-linux"]), - (["LayoutTests/platform/chromium-win/fast/compact/001-expected.txt"], ["chromium-win"]), - (["LayoutTests/platform/mac-leopard/foo"], ["mac-leopard"]), - (["LayoutTests/platform/mac-snowleopard/foo"], ["mac-snowleopard", "win"]), - (["LayoutTests/platform/mac-wk2/Skipped"], ["mac-snowleopard", "win"]), - (["LayoutTests/platform/mac/foo"], ["mac-leopard", "mac-snowleopard", "win"]), - (["LayoutTests/platform/win-xp/foo"], ["win"]), - (["LayoutTests/platform/win-wk2/foo"], ["win"]), - (["LayoutTests/platform/win/foo"], ["win"]), - (["WebCore/mac/foo"], ["chromium-mac", "mac-leopard", "mac-snowleopard"]), - (["WebCore/win/foo"], ["chromium-win", "win"]), - (["WebCore/platform/graphics/gpu/foo"], ["mac-leopard", "mac-snowleopard"]), - (["WebCore/platform/wx/wxcode/win/foo"], []), - (["WebCore/rendering/RenderThemeMac.mm", "WebCore/rendering/RenderThemeMac.h"], ["mac-leopard", "mac-snowleopard"]), - (["WebCore/rendering/RenderThemeChromiumLinux.h"], ["chromium-linux"]), - (["WebCore/rendering/RenderThemeWinCE.h"], []), - ] - - def test_should_build(self): - for files, platforms in self._should_build_tests: - # FIXME: We should test more platforms here once - # build._should_file_trigger_build is implemented for them. - for platform in ["win"]: - should_build = platform in platforms or "*" in platforms - self.assertEqual(build.should_build(platform, files), should_build, "%s should%s have built but did%s (files: %s)" % (platform, "" if should_build else "n't", "n't" if should_build else "", str(files))) - - -if __name__ == "__main__": - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/config/committers.py b/WebKitTools/Scripts/webkitpy/common/config/committers.py deleted file mode 100644 index b451721..0000000 --- a/WebKitTools/Scripts/webkitpy/common/config/committers.py +++ /dev/null @@ -1,331 +0,0 @@ -# Copyright (c) 2009, Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# WebKit's Python module for committer and reviewer validation - - -class Committer: - - def __init__(self, name, email_or_emails, irc_nickname=None): - self.full_name = name - if isinstance(email_or_emails, str): - self.emails = [email_or_emails] - else: - self.emails = email_or_emails - self.irc_nickname = irc_nickname - self.can_review = False - - def bugzilla_email(self): - # FIXME: We're assuming the first email is a valid bugzilla email, - # which might not be right. - return self.emails[0] - - def __str__(self): - return '"%s" <%s>' % (self.full_name, self.emails[0]) - - -class Reviewer(Committer): - - def __init__(self, name, email_or_emails, irc_nickname=None): - Committer.__init__(self, name, email_or_emails, irc_nickname) - self.can_review = True - - -# This is intended as a canonical, machine-readable list of all non-reviewer -# committers for WebKit. If your name is missing here and you are a committer, -# please add it. No review needed. All reviewers are committers, so this list -# is only of committers who are not reviewers. - - -committers_unable_to_review = [ - Committer("Aaron Boodman", "aa@chromium.org", "aboodman"), - Committer("Abhishek Arya", "inferno@chromium.org", "inferno-sec"), - Committer("Adam Langley", "agl@chromium.org", "agl"), - Committer("Albert J. Wong", "ajwong@chromium.org"), - Committer("Alejandro G. Castro", ["alex@igalia.com", "alex@webkit.org"]), - Committer("Alexander Kellett", ["lypanov@mac.com", "a-lists001@lypanov.net", "lypanov@kde.org"], "lypanov"), - Committer("Alexander Pavlov", "apavlov@chromium.org", "apavlov"), - Committer("Andre Boule", "aboule@apple.com"), - Committer("Andrei Popescu", "andreip@google.com", "andreip"), - Committer("Andrew Wellington", ["andrew@webkit.org", "proton@wiretapped.net"], "proton"), - Committer("Andrey Kosyakov", "caseq@chromium.org", "caseq"), - Committer("Andras Becsi", ["abecsi@webkit.org", "abecsi@inf.u-szeged.hu"], "bbandix"), - Committer("Andy Estes", "aestes@apple.com", "estes"), - Committer("Anthony Ricaud", "rik@webkit.org", "rik"), - Committer("Anton Muhin", "antonm@chromium.org", "antonm"), - Committer("Balazs Kelemen", "kbalazs@webkit.org", "kbalazs"), - Committer("Ben Murdoch", "benm@google.com", "benm"), - Committer("Benjamin C Meyer", ["ben@meyerhome.net", "ben@webkit.org"], "icefox"), - Committer("Benjamin Otte", ["otte@gnome.org", "otte@webkit.org"], "otte"), - Committer("Benjamin Poulain", ["benjamin.poulain@nokia.com", "ikipou@gmail.com"]), - Committer("Brent Fulgham", "bfulgham@webkit.org", "bfulgham"), - Committer("Brett Wilson", "brettw@chromium.org", "brettx"), - Committer("Brian Weinstein", "bweinstein@apple.com", "bweinstein"), - Committer("Cameron McCormack", "cam@webkit.org", "heycam"), - Committer("Carol Szabo", "carol.szabo@nokia.com"), - Committer("Chang Shu", "Chang.Shu@nokia.com"), - Committer("Chris Evans", "cevans@google.com"), - Committer("Chris Petersen", "cpetersen@apple.com", "cpetersen"), - Committer("Chris Rogers", "crogers@google.com", "crogers"), - Committer("Christian Dywan", ["christian@twotoasts.de", "christian@webkit.org"]), - Committer("Collin Jackson", "collinj@webkit.org"), - Committer("David Smith", ["catfish.man@gmail.com", "dsmith@webkit.org"], "catfishman"), - Committer("Dean Jackson", "dino@apple.com", "dino"), - Committer("Diego Gonzalez", ["diegohcg@webkit.org", "diego.gonzalez@openbossa.org"], "diegohcg"), - Committer("Dirk Pranke", "dpranke@chromium.org"), - Committer("Drew Wilson", "atwilson@chromium.org", "atwilson"), - Committer("Eli Fidler", "eli@staikos.net", "QBin"), - Committer("Enrica Casucci", "enrica@apple.com"), - Committer("Erik Arvidsson", "arv@chromium.org", "arv"), - Committer("Eric Roman", "eroman@chromium.org", "eroman"), - Committer("Evan Martin", "evan@chromium.org", "evmar"), - Committer("Evan Stade", "estade@chromium.org", "estade"), - Committer("Fady Samuel", "fsamuel@chromium.org", "fsamuel"), - Committer("Feng Qian", "feng@chromium.org"), - Committer("Fumitoshi Ukai", "ukai@chromium.org", "ukai"), - Committer("Gabor Loki", "loki@webkit.org", "loki04"), - Committer("Girish Ramakrishnan", ["girish@forwardbias.in", "ramakrishnan.girish@gmail.com"]), - Committer("Graham Dennis", ["Graham.Dennis@gmail.com", "gdennis@webkit.org"]), - Committer("Greg Bolsinga", "bolsinga@apple.com"), - Committer("Gyuyoung Kim", ["gyuyoung.kim@samsung.com", "gyuyoung@gmail.com", "gyuyoung@webkit.org"], "gyuyoung"), - Committer("Hans Wennborg", "hans@chromium.org", "hwennborg"), - Committer("Hayato Ito", "hayato@chromium.org", "hayato"), - Committer("Hin-Chung Lam", ["hclam@google.com", "hclam@chromium.org"]), - Committer("Ilya Tikhonovsky", "loislo@chromium.org", "loislo"), - Committer("Jakob Petsovits", ["jpetsovits@rim.com", "jpetso@gmx.at"], "jpetso"), - Committer("Jakub Wieczorek", "jwieczorek@webkit.org", "fawek"), - Committer("James Hawkins", ["jhawkins@chromium.org", "jhawkins@google.com"], "jhawkins"), - Committer("Jay Civelli", "jcivelli@chromium.org", "jcivelli"), - Committer("Jens Alfke", ["snej@chromium.org", "jens@apple.com"]), - Committer("Jer Noble", "jer.noble@apple.com", "jernoble"), - Committer("Jeremy Moskovich", ["playmobil@google.com", "jeremy@chromium.org"], "jeremymos"), - Committer("Jessie Berlin", ["jberlin@webkit.org", "jberlin@apple.com"]), - Committer("Jesus Sanchez-Palencia", ["jesus@webkit.org", "jesus.palencia@openbossa.org"], "jeez_"), - Committer("Jocelyn Turcotte", "jocelyn.turcotte@nokia.com", "jturcotte"), - Committer("Jochen Eisinger", "jochen@chromium.org", "jochen__"), - Committer("John Abd-El-Malek", "jam@chromium.org", "jam"), - Committer("John Gregg", ["johnnyg@google.com", "johnnyg@chromium.org"], "johnnyg"), - Committer("Joost de Valk", ["joost@webkit.org", "webkit-dev@joostdevalk.nl"], "Altha"), - Committer("Julie Parent", ["jparent@google.com", "jparent@chromium.org"], "jparent"), - Committer("Julien Chaffraix", ["jchaffraix@webkit.org", "julien.chaffraix@gmail.com"]), - Committer("Jungshik Shin", "jshin@chromium.org"), - Committer("Justin Schuh", "jschuh@chromium.org", "jschuh"), - Committer("Keishi Hattori", "keishi@webkit.org", "keishi"), - Committer("Kelly Norton", "knorton@google.com"), - Committer("Kent Hansen", "kent.hansen@nokia.com", "khansen"), - Committer("Kimmo Kinnunen", ["kimmo.t.kinnunen@nokia.com", "kimmok@iki.fi", "ktkinnun@webkit.org"], "kimmok"), - Committer("Kinuko Yasuda", "kinuko@chromium.org", "kinuko"), - Committer("Krzysztof Kowalczyk", "kkowalczyk@gmail.com"), - Committer("Kwang Yul Seo", ["kwangyul.seo@gmail.com", "skyul@company100.net", "kseo@webkit.org"], "kwangseo"), - Committer("Leandro Pereira", ["leandro@profusion.mobi", "leandro@webkit.org"], "acidx"), - Committer("Levi Weintraub", "lweintraub@apple.com"), - Committer("Lucas De Marchi", ["lucas.demarchi@profusion.mobi", "demarchi@webkit.org"], "demarchi"), - Committer("Luiz Agostini", ["luiz@webkit.org", "luiz.agostini@openbossa.org"], "lca"), - Committer("Mads Ager", "ager@chromium.org"), - Committer("Marcus Voltis Bulach", "bulach@chromium.org"), - Committer("Mario Sanchez Prada", ["msanchez@igalia.com", "mario@webkit.org"], "msanchez"), - Committer("Matt Delaney", "mdelaney@apple.com"), - Committer("Matt Lilek", ["webkit@mattlilek.com", "pewtermoose@webkit.org"]), - Committer("Matt Perry", "mpcomplete@chromium.org"), - Committer("Maxime Britto", ["maxime.britto@gmail.com", "britto@apple.com"]), - Committer("Maxime Simon", ["simon.maxime@gmail.com", "maxime.simon@webkit.org"], "maxime.simon"), - Committer("Michael Nordman", "michaeln@google.com", "michaeln"), - Committer("Michael Saboff", "msaboff@apple.com"), - Committer("Michelangelo De Simone", "michelangelo@webkit.org", "michelangelo"), - Committer("Mihai Parparita", "mihaip@chromium.org", "mihaip"), - Committer("Mike Belshe", ["mbelshe@chromium.org", "mike@belshe.com"]), - Committer("Mike Fenton", ["mifenton@rim.com", "mike.fenton@torchmobile.com"], "mfenton"), - Committer("Mike Thole", ["mthole@mikethole.com", "mthole@apple.com"]), - Committer("Mikhail Naganov", "mnaganov@chromium.org"), - Committer("MORITA Hajime", "morrita@google.com", "morrita"), - Committer("Nico Weber", ["thakis@chromium.org", "thakis@google.com"], "thakis"), - Committer("Noam Rosenthal", "noam.rosenthal@nokia.com", "noamr"), - Committer("Pam Greene", "pam@chromium.org", "pamg"), - Committer("Patrick Gansterer", ["paroga@paroga.com", "paroga@webkit.org"], "paroga"), - Committer("Pavel Podivilov", "podivilov@chromium.org", "podivilov"), - Committer("Peter Kasting", ["pkasting@google.com", "pkasting@chromium.org"], "pkasting"), - Committer("Philippe Normand", ["pnormand@igalia.com", "philn@webkit.org"], "philn-tp"), - Committer("Pierre d'Herbemont", ["pdherbemont@free.fr", "pdherbemont@apple.com"], "pdherbemont"), - Committer("Pierre-Olivier Latour", "pol@apple.com", "pol"), - Committer("Renata Hodovan", "reni@webkit.org", "reni"), - Committer("Robert Hogan", ["robert@webkit.org", "robert@roberthogan.net", "lists@roberthogan.net"], "mwenge"), - Committer("Roland Steiner", "rolandsteiner@chromium.org"), - Committer("Ryosuke Niwa", "rniwa@webkit.org", "rniwa"), - Committer("Satish Sampath", "satish@chromium.org"), - Committer("Scott Violet", "sky@chromium.org", "sky"), - Committer("Stephen White", "senorblanco@chromium.org", "senorblanco"), - Committer("Tony Gentilcore", "tonyg@chromium.org", "tonyg-cr"), - Committer("Trey Matteson", "trey@usa.net", "trey"), - Committer("Tristan O'Tierney", ["tristan@otierney.net", "tristan@apple.com"]), - Committer("Vangelis Kokkevis", "vangelis@chromium.org", "vangelis"), - Committer("Victor Wang", "victorw@chromium.org", "victorw"), - Committer("Vitaly Repeshko", "vitalyr@chromium.org"), - Committer("William Siegrist", "wsiegrist@apple.com", "wms"), - Committer("Xiaomei Ji", "xji@chromium.org", "xji"), - Committer("Yael Aharon", "yael.aharon@nokia.com"), - Committer("Yaar Schnitman", ["yaar@chromium.org", "yaar@google.com"]), - Committer("Yong Li", ["yong.li.webkit@gmail.com", "yong.li@torchmobile.com"], "yong"), - Committer("Yongjun Zhang", "yongjun.zhang@nokia.com"), - Committer("Yuta Kitamura", "yutak@chromium.org", "yutak"), - Committer("Yuzo Fujishima", "yuzo@google.com", "yuzo"), - Committer("Zhenyao Mo", "zmo@google.com", "zhenyao"), - Committer("Zoltan Herczeg", "zherczeg@webkit.org", "zherczeg"), - Committer("Zoltan Horvath", ["zoltan@webkit.org", "hzoltan@inf.u-szeged.hu", "horvath.zoltan.6@stud.u-szeged.hu"], "zoltan"), -] - - -# This is intended as a canonical, machine-readable list of all reviewers for -# WebKit. If your name is missing here and you are a reviewer, please add it. -# No review needed. - - -reviewers_list = [ - Reviewer("Ada Chan", "adachan@apple.com", "chanada"), - Reviewer("Adam Barth", "abarth@webkit.org", "abarth"), - Reviewer("Adam Roben", "aroben@apple.com", "aroben"), - Reviewer("Adam Treat", ["treat@kde.org", "treat@webkit.org", "atreat@rim.com"], "manyoso"), - Reviewer("Adele Peterson", "adele@apple.com", "adele"), - Reviewer("Alexey Proskuryakov", ["ap@webkit.org", "ap@apple.com"], "ap"), - Reviewer("Alice Liu", "alice.liu@apple.com", "aliu"), - Reviewer("Alp Toker", ["alp@nuanti.com", "alp@atoker.com", "alp@webkit.org"], "alp"), - Reviewer("Anders Carlsson", ["andersca@apple.com", "acarlsson@apple.com"], "andersca"), - Reviewer("Andreas Kling", ["kling@webkit.org", "andreas.kling@nokia.com"], "kling"), - Reviewer("Antonio Gomes", ["tonikitoo@webkit.org", "agomes@rim.com"], "tonikitoo"), - Reviewer("Antti Koivisto", ["koivisto@iki.fi", "antti@apple.com", "antti.j.koivisto@nokia.com"], "anttik"), - Reviewer("Ariya Hidayat", ["ariya.hidayat@gmail.com", "ariya@sencha.com", "ariya@webkit.org"], "ariya"), - Reviewer("Beth Dakin", "bdakin@apple.com", "dethbakin"), - Reviewer("Brady Eidson", "beidson@apple.com", "bradee-oh"), - Reviewer("Cameron Zwarich", ["zwarich@apple.com", "cwzwarich@apple.com", "cwzwarich@webkit.org"]), - Reviewer("Chris Blumenberg", "cblu@apple.com", "cblu"), - Reviewer("Chris Marrin", "cmarrin@apple.com", "cmarrin"), - Reviewer("Chris Fleizach", "cfleizach@apple.com", "cfleizach"), - Reviewer("Chris Jerdonek", "cjerdonek@webkit.org", "cjerdonek"), - Reviewer(u"Csaba Osztrogon\u00e1c", "ossy@webkit.org", "ossy"), - Reviewer("Dan Bernstein", ["mitz@webkit.org", "mitz@apple.com"], "mitzpettel"), - Reviewer("Daniel Bates", "dbates@webkit.org", "dydz"), - Reviewer("Darin Adler", "darin@apple.com", "darin"), - Reviewer("Darin Fisher", ["fishd@chromium.org", "darin@chromium.org"], "fishd"), - Reviewer("David Harrison", "harrison@apple.com", "harrison"), - Reviewer("David Hyatt", "hyatt@apple.com", "hyatt"), - Reviewer("David Kilzer", ["ddkilzer@webkit.org", "ddkilzer@apple.com"], "ddkilzer"), - Reviewer("David Levin", "levin@chromium.org", "dave_levin"), - Reviewer("Dimitri Glazkov", "dglazkov@chromium.org", "dglazkov"), - Reviewer("Dirk Schulze", "krit@webkit.org", "krit"), - Reviewer("Dmitry Titov", "dimich@chromium.org", "dimich"), - Reviewer("Don Melton", "gramps@apple.com", "gramps"), - Reviewer("Dumitru Daniliuc", "dumi@chromium.org", "dumi"), - Reviewer("Eric Carlson", "eric.carlson@apple.com"), - Reviewer("Eric Seidel", "eric@webkit.org", "eseidel"), - Reviewer("Gavin Barraclough", "barraclough@apple.com", "gbarra"), - Reviewer("Geoffrey Garen", "ggaren@apple.com", "ggaren"), - Reviewer("George Staikos", ["staikos@kde.org", "staikos@webkit.org"]), - Reviewer("Gustavo Noronha Silva", ["gns@gnome.org", "kov@webkit.org", "gustavo.noronha@collabora.co.uk"], "kov"), - Reviewer("Holger Freyther", ["zecke@selfish.org", "zecke@webkit.org"], "zecke"), - Reviewer("James Robinson", ["jamesr@chromium.org", "jamesr@google.com"], "jamesr"), - Reviewer("Jan Alonzo", ["jmalonzo@gmail.com", "jmalonzo@webkit.org"], "janm"), - Reviewer("Jeremy Orlow", "jorlow@chromium.org", "jorlow"), - Reviewer("Jian Li", "jianli@chromium.org", "jianli"), - Reviewer("John Sullivan", "sullivan@apple.com", "sullivan"), - Reviewer("Jon Honeycutt", "jhoneycutt@apple.com", "jhoneycutt"), - Reviewer("Joseph Pecoraro", ["joepeck@webkit.org", "pecoraro@apple.com"], "JoePeck"), - Reviewer("Justin Garcia", "justin.garcia@apple.com", "justing"), - Reviewer("Ken Kocienda", "kocienda@apple.com"), - Reviewer("Kenneth Rohde Christiansen", ["kenneth@webkit.org", "kenneth.christiansen@openbossa.org", "kenneth.christiansen@gmail.com"], "kenne"), - Reviewer("Kenneth Russell", "kbr@google.com", "kbr_google"), - Reviewer("Kent Tamura", "tkent@chromium.org", "tkent"), - Reviewer("Kevin Decker", "kdecker@apple.com", "superkevin"), - Reviewer("Kevin McCullough", "kmccullough@apple.com", "maculloch"), - Reviewer("Kevin Ollivier", ["kevino@theolliviers.com", "kevino@webkit.org"], "kollivier"), - Reviewer("Lars Knoll", ["lars@trolltech.com", "lars@kde.org", "lars.knoll@nokia.com"], "lars"), - Reviewer("Laszlo Gombos", "laszlo.1.gombos@nokia.com", "lgombos"), - Reviewer("Maciej Stachowiak", "mjs@apple.com", "othermaciej"), - Reviewer("Mark Rowe", "mrowe@apple.com", "bdash"), - Reviewer("Martin Robinson", ["mrobinson@webkit.org", "mrobinson@igalia.com", "martin.james.robinson@gmail.com"], "mrobinson"), - Reviewer("Nate Chapin", "japhet@chromium.org", "japhet"), - Reviewer("Nikolas Zimmermann", ["zimmermann@kde.org", "zimmermann@physik.rwth-aachen.de", "zimmermann@webkit.org"], "wildfox"), - Reviewer("Ojan Vafai", "ojan@chromium.org", "ojan"), - Reviewer("Oliver Hunt", "oliver@apple.com", "olliej"), - Reviewer("Pavel Feldman", "pfeldman@chromium.org", "pfeldman"), - Reviewer("Richard Williamson", "rjw@apple.com", "rjw"), - Reviewer("Rob Buis", ["rwlbuis@gmail.com", "rwlbuis@webkit.org"], "rwlbuis"), - Reviewer("Sam Weinig", ["sam@webkit.org", "weinig@apple.com"], "weinig"), - Reviewer("Shinichiro Hamaji", "hamaji@chromium.org", "hamaji"), - Reviewer("Simon Fraser", "simon.fraser@apple.com", "smfr"), - Reviewer("Simon Hausmann", ["hausmann@webkit.org", "hausmann@kde.org", "simon.hausmann@nokia.com"], "tronical"), - Reviewer("Stephanie Lewis", "slewis@apple.com", "sundiamonde"), - Reviewer("Steve Block", "steveblock@google.com", "steveblock"), - Reviewer("Steve Falkenburg", "sfalken@apple.com", "sfalken"), - Reviewer("Tim Omernick", "timo@apple.com"), - Reviewer("Timothy Hatcher", ["timothy@apple.com", "timothy@hatcher.name"], "xenon"), - Reviewer("Tony Chang", "tony@chromium.org", "tony^work"), - Reviewer(u"Tor Arne Vestb\u00f8", ["vestbo@webkit.org", "tor.arne.vestbo@nokia.com"], "torarne"), - Reviewer("Vicki Murley", "vicki@apple.com"), - Reviewer("Xan Lopez", ["xan.lopez@gmail.com", "xan@gnome.org", "xan@webkit.org"], "xan"), - Reviewer("Yury Semikhatsky", "yurys@chromium.org", "yurys"), - Reviewer("Zack Rusin", "zack@kde.org", "zackr"), -] - - -class CommitterList: - - # Committers and reviewers are passed in to allow easy testing - - def __init__(self, - committers=committers_unable_to_review, - reviewers=reviewers_list): - self._committers = committers + reviewers - self._reviewers = reviewers - self._committers_by_email = {} - - def committers(self): - return self._committers - - def reviewers(self): - return self._reviewers - - def _email_to_committer_map(self): - if not len(self._committers_by_email): - for committer in self._committers: - for email in committer.emails: - self._committers_by_email[email] = committer - return self._committers_by_email - - def committer_by_name(self, name): - # This could be made into a hash lookup if callers need it to be fast. - for committer in self.committers(): - if committer.full_name == name: - return committer - - def committer_by_email(self, email): - return self._email_to_committer_map().get(email) - - def reviewer_by_email(self, email): - committer = self.committer_by_email(email) - if committer and not committer.can_review: - return None - return committer diff --git a/WebKitTools/Scripts/webkitpy/common/config/committers_unittest.py b/WebKitTools/Scripts/webkitpy/common/config/committers_unittest.py deleted file mode 100644 index 068c0ee..0000000 --- a/WebKitTools/Scripts/webkitpy/common/config/committers_unittest.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest -from webkitpy.common.config.committers import CommitterList, Committer, Reviewer - -class CommittersTest(unittest.TestCase): - - def test_committer_lookup(self): - committer = Committer('Test One', 'one@test.com', 'one') - reviewer = Reviewer('Test Two', ['two@test.com', 'two@rad.com', 'so_two@gmail.com']) - committer_list = CommitterList(committers=[committer], reviewers=[reviewer]) - - # Test valid committer and reviewer lookup - self.assertEqual(committer_list.committer_by_email('one@test.com'), committer) - self.assertEqual(committer_list.reviewer_by_email('two@test.com'), reviewer) - self.assertEqual(committer_list.committer_by_email('two@test.com'), reviewer) - self.assertEqual(committer_list.committer_by_email('two@rad.com'), reviewer) - self.assertEqual(committer_list.reviewer_by_email('so_two@gmail.com'), reviewer) - - # Test valid committer and reviewer lookup - self.assertEqual(committer_list.committer_by_name("Test One"), committer) - self.assertEqual(committer_list.committer_by_name("Test Two"), reviewer) - self.assertEqual(committer_list.committer_by_name("Test Three"), None) - - # Test that the first email is assumed to be the Bugzilla email address (for now) - self.assertEqual(committer_list.committer_by_email('two@rad.com').bugzilla_email(), 'two@test.com') - - # Test that a known committer is not returned during reviewer lookup - self.assertEqual(committer_list.reviewer_by_email('one@test.com'), None) - - # Test that unknown email address fail both committer and reviewer lookup - self.assertEqual(committer_list.committer_by_email('bar@bar.com'), None) - self.assertEqual(committer_list.reviewer_by_email('bar@bar.com'), None) - - # Test that emails returns a list. - self.assertEqual(committer.emails, ['one@test.com']) - - self.assertEqual(committer.irc_nickname, 'one') - - # Test that committers returns committers and reviewers and reviewers() just reviewers. - self.assertEqual(committer_list.committers(), [committer, reviewer]) - self.assertEqual(committer_list.reviewers(), [reviewer]) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/config/committervalidator.py b/WebKitTools/Scripts/webkitpy/common/config/committervalidator.py deleted file mode 100644 index b7b2990..0000000 --- a/WebKitTools/Scripts/webkitpy/common/config/committervalidator.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright (c) 2009 Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# Copyright (c) 2010 Research In Motion Limited. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import os - -from webkitpy.common.system.ospath import relpath -from webkitpy.common.config import committers - - -class CommitterValidator(object): - - def __init__(self, bugzilla): - self._bugzilla = bugzilla - - # _view_source_url belongs in some sort of webkit_config.py module. - def _view_source_url(self, local_path): - return "http://trac.webkit.org/browser/trunk/%s" % local_path - - def _checkout_root(self): - # FIXME: This is a hack, we would have this from scm.checkout_root - # if we had any way to get to an scm object here. - components = __file__.split(os.sep) - tools_index = components.index("WebKitTools") - return os.sep.join(components[:tools_index]) - - def _committers_py_path(self): - # extension can sometimes be .pyc, we always want .py - (path, extension) = os.path.splitext(committers.__file__) - # FIXME: When we're allowed to use python 2.6 we can use the real - # os.path.relpath - path = relpath(path, self._checkout_root()) - return ".".join([path, "py"]) - - def _flag_permission_rejection_message(self, setter_email, flag_name): - # Should come from some webkit_config.py - contribution_guidlines = "http://webkit.org/coding/contributing.html" - # This could be queried from the status_server. - queue_administrator = "eseidel@chromium.org" - # This could be queried from the tool. - queue_name = "commit-queue" - committers_list = self._committers_py_path() - message = "%s does not have %s permissions according to %s." % ( - setter_email, - flag_name, - self._view_source_url(committers_list)) - message += "\n\n- If you do not have %s rights please read %s for instructions on how to use bugzilla flags." % ( - flag_name, contribution_guidlines) - message += "\n\n- If you have %s rights please correct the error in %s by adding yourself to the file (no review needed). " % ( - flag_name, committers_list) - message += "The %s restarts itself every 2 hours. After restart the %s will correctly respect your %s rights." % ( - queue_name, queue_name, flag_name) - return message - - def _validate_setter_email(self, patch, result_key, rejection_function): - committer = getattr(patch, result_key)() - # If the flag is set, and we don't recognize the setter, reject the - # flag! - setter_email = patch._attachment_dictionary.get("%s_email" % result_key) - if setter_email and not committer: - rejection_function(patch.id(), - self._flag_permission_rejection_message(setter_email, - result_key)) - return False - return True - - def _reject_patch_if_flags_are_invalid(self, patch): - return (self._validate_setter_email( - patch, "reviewer", self.reject_patch_from_review_queue) - and self._validate_setter_email( - patch, "committer", self.reject_patch_from_commit_queue)) - - def patches_after_rejecting_invalid_commiters_and_reviewers(self, patches): - return [patch for patch in patches if self._reject_patch_if_flags_are_invalid(patch)] - - def reject_patch_from_commit_queue(self, - attachment_id, - additional_comment_text=None): - comment_text = "Rejecting patch %s from commit-queue." % attachment_id - self._bugzilla.set_flag_on_attachment(attachment_id, - "commit-queue", - "-", - comment_text, - additional_comment_text) - - def reject_patch_from_review_queue(self, - attachment_id, - additional_comment_text=None): - comment_text = "Rejecting patch %s from review queue." % attachment_id - self._bugzilla.set_flag_on_attachment(attachment_id, - 'review', - '-', - comment_text, - additional_comment_text) diff --git a/WebKitTools/Scripts/webkitpy/common/config/committervalidator_unittest.py b/WebKitTools/Scripts/webkitpy/common/config/committervalidator_unittest.py deleted file mode 100644 index 61fa3bf..0000000 --- a/WebKitTools/Scripts/webkitpy/common/config/committervalidator_unittest.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from .committervalidator import CommitterValidator - - -class CommitterValidatorTest(unittest.TestCase): - def test_flag_permission_rejection_message(self): - validator = CommitterValidator(bugzilla=None) - self.assertEqual(validator._committers_py_path(), "WebKitTools/Scripts/webkitpy/common/config/committers.py") - expected_messsage = """foo@foo.com does not have review permissions according to http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/common/config/committers.py. - -- If you do not have review rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags. - -- If you have review rights please correct the error in WebKitTools/Scripts/webkitpy/common/config/committers.py by adding yourself to the file (no review needed). The commit-queue restarts itself every 2 hours. After restart the commit-queue will correctly respect your review rights.""" - self.assertEqual(validator._flag_permission_rejection_message("foo@foo.com", "review"), expected_messsage) diff --git a/WebKitTools/Scripts/webkitpy/common/config/irc.py b/WebKitTools/Scripts/webkitpy/common/config/irc.py deleted file mode 100644 index 950c573..0000000 --- a/WebKitTools/Scripts/webkitpy/common/config/irc.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -server="irc.freenode.net" -port=6667 -channel="#webkit" diff --git a/WebKitTools/Scripts/webkitpy/common/config/ports.py b/WebKitTools/Scripts/webkitpy/common/config/ports.py deleted file mode 100644 index d268865..0000000 --- a/WebKitTools/Scripts/webkitpy/common/config/ports.py +++ /dev/null @@ -1,249 +0,0 @@ -# Copyright (C) 2009, Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# WebKit's Python module for understanding the various ports - -import os -import platform - -from webkitpy.common.system.executive import Executive - - -class WebKitPort(object): - - # We might need to pass scm into this function for scm.checkout_root - @classmethod - def script_path(cls, script_name): - return os.path.join("WebKitTools", "Scripts", script_name) - - @staticmethod - def port(port_name): - ports = { - "chromium": ChromiumPort, - "chromium-xvfb": ChromiumXVFBPort, - "gtk": GtkPort, - "mac": MacPort, - "win": WinPort, - "qt": QtPort, - "efl": EflPort, - } - default_port = { - "Windows": WinPort, - "Darwin": MacPort, - } - # Do we really need MacPort as the ultimate default? - return ports.get(port_name, default_port.get(platform.system(), MacPort)) - - @staticmethod - def makeArgs(): - args = '--makeargs="-j%s"' % Executive().cpu_count() - if os.environ.has_key('MAKEFLAGS'): - args = '--makeargs="%s"' % os.environ['MAKEFLAGS'] - return args - - @classmethod - def name(cls): - raise NotImplementedError("subclasses must implement") - - @classmethod - def flag(cls): - raise NotImplementedError("subclasses must implement") - - @classmethod - def update_webkit_command(cls): - return [cls.script_path("update-webkit")] - - @classmethod - def build_webkit_command(cls, build_style=None): - command = [cls.script_path("build-webkit")] - if build_style == "debug": - command.append("--debug") - if build_style == "release": - command.append("--release") - return command - - @classmethod - def run_javascriptcore_tests_command(cls): - return [cls.script_path("run-javascriptcore-tests")] - - @classmethod - def run_webkit_tests_command(cls): - return [cls.script_path("run-webkit-tests")] - - @classmethod - def run_python_unittests_command(cls): - return [cls.script_path("test-webkitpy")] - - @classmethod - def run_perl_unittests_command(cls): - return [cls.script_path("test-webkitperl")] - - @classmethod - def layout_tests_results_path(cls): - return "/tmp/layout-test-results/results.html" - - -class MacPort(WebKitPort): - - @classmethod - def name(cls): - return "Mac" - - @classmethod - def flag(cls): - return "--port=mac" - - @classmethod - def _system_version(cls): - version_string = platform.mac_ver()[0] # e.g. "10.5.6" - version_tuple = version_string.split('.') - return map(int, version_tuple) - - @classmethod - def is_leopard(cls): - return tuple(cls._system_version()[:2]) == (10, 5) - - -class WinPort(WebKitPort): - - @classmethod - def name(cls): - return "Win" - - @classmethod - def flag(cls): - # FIXME: This is lame. We should autogenerate this from a codename or something. - return "--port=win" - - -class GtkPort(WebKitPort): - - @classmethod - def name(cls): - return "Gtk" - - @classmethod - def flag(cls): - return "--port=gtk" - - @classmethod - def build_webkit_command(cls, build_style=None): - command = WebKitPort.build_webkit_command(build_style=build_style) - command.append("--gtk") - command.append(WebKitPort.makeArgs()) - return command - - @classmethod - def run_webkit_tests_command(cls): - command = WebKitPort.run_webkit_tests_command() - command.append("--gtk") - return command - - -class QtPort(WebKitPort): - - @classmethod - def name(cls): - return "Qt" - - @classmethod - def flag(cls): - return "--port=qt" - - @classmethod - def build_webkit_command(cls, build_style=None): - command = WebKitPort.build_webkit_command(build_style=build_style) - command.append("--qt") - command.append(WebKitPort.makeArgs()) - return command - - -class EflPort(WebKitPort): - - @classmethod - def name(cls): - return "Efl" - - @classmethod - def flag(cls): - return "--port=efl" - - @classmethod - def build_webkit_command(cls, build_style=None): - command = WebKitPort.build_webkit_command(build_style=build_style) - command.append("--efl") - command.append(WebKitPort.makeArgs()) - return command - - -class ChromiumPort(WebKitPort): - - @classmethod - def name(cls): - return "Chromium" - - @classmethod - def flag(cls): - return "--port=chromium" - - @classmethod - def update_webkit_command(cls): - command = WebKitPort.update_webkit_command() - command.append("--chromium") - return command - - @classmethod - def build_webkit_command(cls, build_style=None): - command = WebKitPort.build_webkit_command(build_style=build_style) - command.append("--chromium") - return command - - @classmethod - def run_webkit_tests_command(cls): - return [ - cls.script_path("new-run-webkit-tests"), - "--chromium", - "--use-drt", - "--no-pixel-tests", - ] - - @classmethod - def run_javascriptcore_tests_command(cls): - return None - - -class ChromiumXVFBPort(ChromiumPort): - - @classmethod - def flag(cls): - return "--port=chromium-xvfb" - - @classmethod - def run_webkit_tests_command(cls): - # FIXME: We should find a better way to do this. - return ["xvfb-run"] + ChromiumPort.run_webkit_tests_command() diff --git a/WebKitTools/Scripts/webkitpy/common/config/ports_unittest.py b/WebKitTools/Scripts/webkitpy/common/config/ports_unittest.py deleted file mode 100644 index 3bdf0e6..0000000 --- a/WebKitTools/Scripts/webkitpy/common/config/ports_unittest.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2009, Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.config.ports import * - - -class WebKitPortTest(unittest.TestCase): - def test_mac_port(self): - self.assertEquals(MacPort.name(), "Mac") - self.assertEquals(MacPort.flag(), "--port=mac") - self.assertEquals(MacPort.run_webkit_tests_command(), [WebKitPort.script_path("run-webkit-tests")]) - self.assertEquals(MacPort.build_webkit_command(), [WebKitPort.script_path("build-webkit")]) - self.assertEquals(MacPort.build_webkit_command(build_style="debug"), [WebKitPort.script_path("build-webkit"), "--debug"]) - self.assertEquals(MacPort.build_webkit_command(build_style="release"), [WebKitPort.script_path("build-webkit"), "--release"]) - - class TestIsLeopard(MacPort): - @classmethod - def _system_version(cls): - return [10, 5] - self.assertTrue(TestIsLeopard.is_leopard()) - - def test_gtk_port(self): - self.assertEquals(GtkPort.name(), "Gtk") - self.assertEquals(GtkPort.flag(), "--port=gtk") - self.assertEquals(GtkPort.run_webkit_tests_command(), [WebKitPort.script_path("run-webkit-tests"), "--gtk"]) - self.assertEquals(GtkPort.build_webkit_command(), [WebKitPort.script_path("build-webkit"), "--gtk", WebKitPort.makeArgs()]) - self.assertEquals(GtkPort.build_webkit_command(build_style="debug"), [WebKitPort.script_path("build-webkit"), "--debug", "--gtk", WebKitPort.makeArgs()]) - - def test_qt_port(self): - self.assertEquals(QtPort.name(), "Qt") - self.assertEquals(QtPort.flag(), "--port=qt") - self.assertEquals(QtPort.run_webkit_tests_command(), [WebKitPort.script_path("run-webkit-tests")]) - self.assertEquals(QtPort.build_webkit_command(), [WebKitPort.script_path("build-webkit"), "--qt", WebKitPort.makeArgs()]) - self.assertEquals(QtPort.build_webkit_command(build_style="debug"), [WebKitPort.script_path("build-webkit"), "--debug", "--qt", WebKitPort.makeArgs()]) - - def test_chromium_port(self): - self.assertEquals(ChromiumPort.name(), "Chromium") - self.assertEquals(ChromiumPort.flag(), "--port=chromium") - self.assertEquals(ChromiumPort.run_webkit_tests_command(), [WebKitPort.script_path("new-run-webkit-tests"), "--chromium", "--use-drt", "--no-pixel-tests"]) - self.assertEquals(ChromiumPort.build_webkit_command(), [WebKitPort.script_path("build-webkit"), "--chromium"]) - self.assertEquals(ChromiumPort.build_webkit_command(build_style="debug"), [WebKitPort.script_path("build-webkit"), "--debug", "--chromium"]) - self.assertEquals(ChromiumPort.update_webkit_command(), [WebKitPort.script_path("update-webkit"), "--chromium"]) - - def test_chromium_xvfb_port(self): - self.assertEquals(ChromiumXVFBPort.run_webkit_tests_command(), ["xvfb-run", "WebKitTools/Scripts/new-run-webkit-tests", "--chromium", "--use-drt", "--no-pixel-tests"]) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/memoized.py b/WebKitTools/Scripts/webkitpy/common/memoized.py deleted file mode 100644 index dc844a5..0000000 --- a/WebKitTools/Scripts/webkitpy/common/memoized.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# Python does not (yet) seem to provide automatic memoization. So we've -# written a small decorator to do so. - -import functools - - -class memoized(object): - def __init__(self, function): - self._function = function - self._results_cache = {} - - def __call__(self, *args): - try: - return self._results_cache[args] - except KeyError: - # If we didn't find the args in our cache, call and save the results. - result = self._function(*args) - self._results_cache[args] = result - return result - # FIXME: We may need to handle TypeError here in the case - # that "args" is not a valid dictionary key. - - # Use python "descriptor" protocol __get__ to appear - # invisible during property access. - def __get__(self, instance, owner): - # Return a function partial with obj already bound as self. - return functools.partial(self.__call__, instance) diff --git a/WebKitTools/Scripts/webkitpy/common/memoized_unittest.py b/WebKitTools/Scripts/webkitpy/common/memoized_unittest.py deleted file mode 100644 index dd7c793..0000000 --- a/WebKitTools/Scripts/webkitpy/common/memoized_unittest.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.memoized import memoized - - -class _TestObject(object): - def __init__(self): - self.callCount = 0 - - @memoized - def memoized_add(self, argument): - """testing docstring""" - self.callCount += 1 - if argument is None: - return None # Avoid the TypeError from None + 1 - return argument + 1 - - -class MemoizedTest(unittest.TestCase): - def test_caching(self): - test = _TestObject() - test.callCount = 0 - self.assertEqual(test.memoized_add(1), 2) - self.assertEqual(test.callCount, 1) - self.assertEqual(test.memoized_add(1), 2) - self.assertEqual(test.callCount, 1) - - # Validate that callCount is working as expected. - self.assertEqual(test.memoized_add(2), 3) - self.assertEqual(test.callCount, 2) - - def test_tearoff(self): - test = _TestObject() - # Make sure that get()/tear-offs work: - tearoff = test.memoized_add - self.assertEqual(tearoff(4), 5) - self.assertEqual(test.callCount, 1) diff --git a/WebKitTools/Scripts/webkitpy/common/net/__init__.py b/WebKitTools/Scripts/webkitpy/common/net/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/__init__.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla/__init__.py deleted file mode 100644 index cfaf3b1..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# Required for Python to search this directory for module files - -# We only export public API here. -# FIXME: parse_bug_id should not be a free function. -from .bugzilla import Bugzilla, parse_bug_id -# Unclear if Bug and Attachment need to be public classes. -from .bug import Bug -from .attachment import Attachment diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/attachment.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla/attachment.py deleted file mode 100644 index 85761fe..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/attachment.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright (c) 2009 Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# Copyright (c) 2010 Research In Motion Limited. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.common.system.deprecated_logging import log - - -class Attachment(object): - - rollout_preamble = "ROLLOUT of r" - - def __init__(self, attachment_dictionary, bug): - self._attachment_dictionary = attachment_dictionary - self._bug = bug - self._reviewer = None - self._committer = None - - def _bugzilla(self): - return self._bug._bugzilla - - def id(self): - return int(self._attachment_dictionary.get("id")) - - def attacher_is_committer(self): - return self._bugzilla.committers.committer_by_email( - patch.attacher_email()) - - def attacher_email(self): - return self._attachment_dictionary.get("attacher_email") - - def bug(self): - return self._bug - - def bug_id(self): - return int(self._attachment_dictionary.get("bug_id")) - - def is_patch(self): - return not not self._attachment_dictionary.get("is_patch") - - def is_obsolete(self): - return not not self._attachment_dictionary.get("is_obsolete") - - def is_rollout(self): - return self.name().startswith(self.rollout_preamble) - - def name(self): - return self._attachment_dictionary.get("name") - - def attach_date(self): - return self._attachment_dictionary.get("attach_date") - - def review(self): - return self._attachment_dictionary.get("review") - - def commit_queue(self): - return self._attachment_dictionary.get("commit-queue") - - def url(self): - # FIXME: This should just return - # self._bugzilla().attachment_url_for_id(self.id()). scm_unittest.py - # depends on the current behavior. - return self._attachment_dictionary.get("url") - - def contents(self): - # FIXME: We shouldn't be grabbing at _bugzilla. - return self._bug._bugzilla.fetch_attachment_contents(self.id()) - - def _validate_flag_value(self, flag): - email = self._attachment_dictionary.get("%s_email" % flag) - if not email: - return None - committer = getattr(self._bugzilla().committers, - "%s_by_email" % flag)(email) - if committer: - return committer - log("Warning, attachment %s on bug %s has invalid %s (%s)" % ( - self._attachment_dictionary['id'], - self._attachment_dictionary['bug_id'], flag, email)) - - def reviewer(self): - if not self._reviewer: - self._reviewer = self._validate_flag_value("reviewer") - return self._reviewer - - def committer(self): - if not self._committer: - self._committer = self._validate_flag_value("committer") - return self._committer diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bug.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bug.py deleted file mode 100644 index 2cb4bc8..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bug.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright (c) 2009 Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# Copyright (c) 2010 Research In Motion Limited. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from .attachment import Attachment - - -class Bug(object): - # FIXME: This class is kinda a hack for now. It exists so we have one - # place to hold bug logic, even if much of the code deals with - # dictionaries still. - - def __init__(self, bug_dictionary, bugzilla): - self.bug_dictionary = bug_dictionary - self._bugzilla = bugzilla - - def id(self): - return self.bug_dictionary["id"] - - def title(self): - return self.bug_dictionary["title"] - - def assigned_to_email(self): - return self.bug_dictionary["assigned_to_email"] - - # FIXME: This information should be stored in some sort of webkit_config.py instead of here. - unassigned_emails = frozenset([ - "webkit-unassigned@lists.webkit.org", - "webkit-qt-unassigned@trolltech.com", - ]) - - def is_unassigned(self): - return self.assigned_to_email() in self.unassigned_emails - - def status(self): - return self.bug_dictionary["bug_status"] - - # Bugzilla has many status states we don't really use in WebKit: - # https://bugs.webkit.org/page.cgi?id=fields.html#status - _open_states = ["UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED"] - _closed_states = ["RESOLVED", "VERIFIED", "CLOSED"] - - def is_open(self): - return self.status() in self._open_states - - def is_closed(self): - return not self.is_open() - - # Rarely do we actually want obsolete attachments - def attachments(self, include_obsolete=False): - attachments = self.bug_dictionary["attachments"] - if not include_obsolete: - attachments = filter(lambda attachment: - not attachment["is_obsolete"], attachments) - return [Attachment(attachment, self) for attachment in attachments] - - def patches(self, include_obsolete=False): - return [patch for patch in self.attachments(include_obsolete) - if patch.is_patch()] - - def unreviewed_patches(self): - return [patch for patch in self.patches() if patch.review() == "?"] - - def reviewed_patches(self, include_invalid=False): - patches = [patch for patch in self.patches() if patch.review() == "+"] - if include_invalid: - return patches - # Checking reviewer() ensures that it was both reviewed and has a valid - # reviewer. - return filter(lambda patch: patch.reviewer(), patches) - - def commit_queued_patches(self, include_invalid=False): - patches = [patch for patch in self.patches() - if patch.commit_queue() == "+"] - if include_invalid: - return patches - # Checking committer() ensures that it was both commit-queue+'d and has - # a valid committer. - return filter(lambda patch: patch.committer(), patches) diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bug_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bug_unittest.py deleted file mode 100644 index d43d64f..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bug_unittest.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from .bug import Bug - - -class BugTest(unittest.TestCase): - def test_is_unassigned(self): - for email in Bug.unassigned_emails: - bug = Bug({"assigned_to_email": email}, bugzilla=None) - self.assertTrue(bug.is_unassigned()) - bug = Bug({"assigned_to_email": "test@test.com"}, bugzilla=None) - self.assertFalse(bug.is_unassigned()) diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bugzilla.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bugzilla.py deleted file mode 100644 index 9fa7fe5..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bugzilla.py +++ /dev/null @@ -1,674 +0,0 @@ -# Copyright (c) 2009 Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# Copyright (c) 2010 Research In Motion Limited. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# WebKit's Python module for interacting with Bugzilla - -import os.path -import re -import StringIO - -from datetime import datetime # used in timestamp() - -from .attachment import Attachment -from .bug import Bug - -from webkitpy.common.system.deprecated_logging import log -from webkitpy.common.config import committers -from webkitpy.common.net.credentials import Credentials -from webkitpy.common.system.user import User -from webkitpy.thirdparty.autoinstalled.mechanize import Browser -from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup, SoupStrainer - - -# FIXME: parse_bug_id should not be a free function. -def parse_bug_id(message): - if not message: - return None - match = re.search("http\://webkit\.org/b/(?P<bug_id>\d+)", message) - if match: - return int(match.group('bug_id')) - match = re.search( - Bugzilla.bug_server_regex + "show_bug\.cgi\?id=(?P<bug_id>\d+)", - message) - if match: - return int(match.group('bug_id')) - return None - - -def timestamp(): - return datetime.now().strftime("%Y%m%d%H%M%S") - - -# A container for all of the logic for making and parsing buzilla queries. -class BugzillaQueries(object): - - def __init__(self, bugzilla): - self._bugzilla = bugzilla - - # Note: _load_query and _fetch_bug are the only two methods which access - # self._bugzilla. - - def _load_query(self, query): - self._bugzilla.authenticate() - - full_url = "%s%s" % (self._bugzilla.bug_server_url, query) - return self._bugzilla.browser.open(full_url) - - def _fetch_bug(self, bug_id): - return self._bugzilla.fetch_bug(bug_id) - - def _fetch_bug_ids_advanced_query(self, query): - soup = BeautifulSoup(self._load_query(query)) - # The contents of the <a> inside the cells in the first column happen - # to be the bug id. - return [int(bug_link_cell.find("a").string) - for bug_link_cell in soup('td', "first-child")] - - def _parse_attachment_ids_request_query(self, page): - digits = re.compile("\d+") - attachment_href = re.compile("attachment.cgi\?id=\d+&action=review") - attachment_links = SoupStrainer("a", href=attachment_href) - return [int(digits.search(tag["href"]).group(0)) - for tag in BeautifulSoup(page, parseOnlyThese=attachment_links)] - - def _fetch_attachment_ids_request_query(self, query): - return self._parse_attachment_ids_request_query(self._load_query(query)) - - def _parse_quips(self, page): - soup = BeautifulSoup(page, convertEntities=BeautifulSoup.HTML_ENTITIES) - quips = soup.find(text=re.compile(r"Existing quips:")).findNext("ul").findAll("li") - return [unicode(quip_entry.string) for quip_entry in quips] - - def fetch_quips(self): - return self._parse_quips(self._load_query("/quips.cgi?action=show")) - - # List of all r+'d bugs. - def fetch_bug_ids_from_pending_commit_list(self): - needs_commit_query_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=equals&value0-0-0=review%2B" - return self._fetch_bug_ids_advanced_query(needs_commit_query_url) - - def fetch_patches_from_pending_commit_list(self): - return sum([self._fetch_bug(bug_id).reviewed_patches() - for bug_id in self.fetch_bug_ids_from_pending_commit_list()], []) - - def fetch_bug_ids_from_commit_queue(self): - commit_queue_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=equals&value0-0-0=commit-queue%2B&order=Last+Changed" - return self._fetch_bug_ids_advanced_query(commit_queue_url) - - def fetch_patches_from_commit_queue(self): - # This function will only return patches which have valid committers - # set. It won't reject patches with invalid committers/reviewers. - return sum([self._fetch_bug(bug_id).commit_queued_patches() - for bug_id in self.fetch_bug_ids_from_commit_queue()], []) - - def fetch_bug_ids_from_review_queue(self): - review_queue_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=equals&value0-0-0=review?" - return self._fetch_bug_ids_advanced_query(review_queue_url) - - # This method will make several requests to bugzilla. - def fetch_patches_from_review_queue(self, limit=None): - # [:None] returns the whole array. - return sum([self._fetch_bug(bug_id).unreviewed_patches() - for bug_id in self.fetch_bug_ids_from_review_queue()[:limit]], []) - - # NOTE: This is the only client of _fetch_attachment_ids_request_query - # This method only makes one request to bugzilla. - def fetch_attachment_ids_from_review_queue(self): - review_queue_url = "request.cgi?action=queue&type=review&group=type" - return self._fetch_attachment_ids_request_query(review_queue_url) - - -class Bugzilla(object): - - def __init__(self, dryrun=False, committers=committers.CommitterList()): - self.dryrun = dryrun - self.authenticated = False - self.queries = BugzillaQueries(self) - self.committers = committers - self.cached_quips = [] - - # FIXME: We should use some sort of Browser mock object when in dryrun - # mode (to prevent any mistakes). - self.browser = Browser() - # Ignore bugs.webkit.org/robots.txt until we fix it to allow this - # script. - self.browser.set_handle_robots(False) - - # FIXME: Much of this should go into some sort of config module: - bug_server_host = "bugs.webkit.org" - bug_server_regex = "https?://%s/" % re.sub('\.', '\\.', bug_server_host) - bug_server_url = "https://%s/" % bug_server_host - - def quips(self): - # We only fetch and parse the list of quips once per instantiation - # so that we do not burden bugs.webkit.org. - if not self.cached_quips and not self.dryrun: - self.cached_quips = self.queries.fetch_quips() - return self.cached_quips - - def bug_url_for_bug_id(self, bug_id, xml=False): - if not bug_id: - return None - content_type = "&ctype=xml" if xml else "" - return "%sshow_bug.cgi?id=%s%s" % (self.bug_server_url, - bug_id, - content_type) - - def short_bug_url_for_bug_id(self, bug_id): - if not bug_id: - return None - return "http://webkit.org/b/%s" % bug_id - - def attachment_url_for_id(self, attachment_id, action="view"): - if not attachment_id: - return None - action_param = "" - if action and action != "view": - action_param = "&action=%s" % action - return "%sattachment.cgi?id=%s%s" % (self.bug_server_url, - attachment_id, - action_param) - - def _parse_attachment_flag(self, - element, - flag_name, - attachment, - result_key): - flag = element.find('flag', attrs={'name': flag_name}) - if flag: - attachment[flag_name] = flag['status'] - if flag['status'] == '+': - attachment[result_key] = flag['setter'] - # Sadly show_bug.cgi?ctype=xml does not expose the flag modification date. - - def _string_contents(self, soup): - # WebKit's bugzilla instance uses UTF-8. - # BeautifulSoup always returns Unicode strings, however - # the .string method returns a (unicode) NavigableString. - # NavigableString can confuse other parts of the code, so we - # convert from NavigableString to a real unicode() object using unicode(). - return unicode(soup.string) - - # Example: 2010-01-20 14:31 PST - # FIXME: Some bugzilla dates seem to have seconds in them? - # Python does not support timezones out of the box. - # Assume that bugzilla always uses PST (which is true for bugs.webkit.org) - _bugzilla_date_format = "%Y-%m-%d %H:%M" - - @classmethod - def _parse_date(cls, date_string): - (date, time, time_zone) = date_string.split(" ") - # Ignore the timezone because python doesn't understand timezones out of the box. - date_string = "%s %s" % (date, time) - return datetime.strptime(date_string, cls._bugzilla_date_format) - - def _date_contents(self, soup): - return self._parse_date(self._string_contents(soup)) - - def _parse_attachment_element(self, element, bug_id): - attachment = {} - attachment['bug_id'] = bug_id - attachment['is_obsolete'] = (element.has_key('isobsolete') and element['isobsolete'] == "1") - attachment['is_patch'] = (element.has_key('ispatch') and element['ispatch'] == "1") - attachment['id'] = int(element.find('attachid').string) - # FIXME: No need to parse out the url here. - attachment['url'] = self.attachment_url_for_id(attachment['id']) - attachment["attach_date"] = self._date_contents(element.find("date")) - attachment['name'] = self._string_contents(element.find('desc')) - attachment['attacher_email'] = self._string_contents(element.find('attacher')) - attachment['type'] = self._string_contents(element.find('type')) - self._parse_attachment_flag( - element, 'review', attachment, 'reviewer_email') - self._parse_attachment_flag( - element, 'commit-queue', attachment, 'committer_email') - return attachment - - def _parse_bug_page(self, page): - soup = BeautifulSoup(page) - bug = {} - bug["id"] = int(soup.find("bug_id").string) - bug["title"] = self._string_contents(soup.find("short_desc")) - bug["bug_status"] = self._string_contents(soup.find("bug_status")) - bug["reporter_email"] = self._string_contents(soup.find("reporter")) - bug["assigned_to_email"] = self._string_contents(soup.find("assigned_to")) - bug["cc_emails"] = [self._string_contents(element) - for element in soup.findAll('cc')] - bug["attachments"] = [self._parse_attachment_element(element, bug["id"]) for element in soup.findAll('attachment')] - return bug - - # Makes testing fetch_*_from_bug() possible until we have a better - # BugzillaNetwork abstration. - - def _fetch_bug_page(self, bug_id): - bug_url = self.bug_url_for_bug_id(bug_id, xml=True) - log("Fetching: %s" % bug_url) - return self.browser.open(bug_url) - - def fetch_bug_dictionary(self, bug_id): - try: - return self._parse_bug_page(self._fetch_bug_page(bug_id)) - except KeyboardInterrupt: - raise - except: - self.authenticate() - return self._parse_bug_page(self._fetch_bug_page(bug_id)) - - # FIXME: A BugzillaCache object should provide all these fetch_ methods. - - def fetch_bug(self, bug_id): - return Bug(self.fetch_bug_dictionary(bug_id), self) - - def fetch_attachment_contents(self, attachment_id): - attachment_url = self.attachment_url_for_id(attachment_id) - # We need to authenticate to download patches from security bugs. - self.authenticate() - return self.browser.open(attachment_url).read() - - def _parse_bug_id_from_attachment_page(self, page): - # The "Up" relation happens to point to the bug. - up_link = BeautifulSoup(page).find('link', rel='Up') - if not up_link: - # This attachment does not exist (or you don't have permissions to - # view it). - return None - match = re.search("show_bug.cgi\?id=(?P<bug_id>\d+)", up_link['href']) - return int(match.group('bug_id')) - - def bug_id_for_attachment_id(self, attachment_id): - self.authenticate() - - attachment_url = self.attachment_url_for_id(attachment_id, 'edit') - log("Fetching: %s" % attachment_url) - page = self.browser.open(attachment_url) - return self._parse_bug_id_from_attachment_page(page) - - # FIXME: This should just return Attachment(id), which should be able to - # lazily fetch needed data. - - def fetch_attachment(self, attachment_id): - # We could grab all the attachment details off of the attachment edit - # page but we already have working code to do so off of the bugs page, - # so re-use that. - bug_id = self.bug_id_for_attachment_id(attachment_id) - if not bug_id: - return None - attachments = self.fetch_bug(bug_id).attachments(include_obsolete=True) - for attachment in attachments: - if attachment.id() == int(attachment_id): - return attachment - return None # This should never be hit. - - def authenticate(self): - if self.authenticated: - return - - if self.dryrun: - log("Skipping log in for dry run...") - self.authenticated = True - return - - credentials = Credentials(self.bug_server_host, git_prefix="bugzilla") - - attempts = 0 - while not self.authenticated: - attempts += 1 - username, password = credentials.read_credentials() - - log("Logging in as %s..." % username) - self.browser.open(self.bug_server_url + - "index.cgi?GoAheadAndLogIn=1") - self.browser.select_form(name="login") - self.browser['Bugzilla_login'] = username - self.browser['Bugzilla_password'] = password - response = self.browser.submit() - - match = re.search("<title>(.+?)</title>", response.read()) - # If the resulting page has a title, and it contains the word - # "invalid" assume it's the login failure page. - if match and re.search("Invalid", match.group(1), re.IGNORECASE): - errorMessage = "Bugzilla login failed: %s" % match.group(1) - # raise an exception only if this was the last attempt - if attempts < 5: - log(errorMessage) - else: - raise Exception(errorMessage) - else: - self.authenticated = True - self.username = username - - def _fill_attachment_form(self, - description, - patch_file_object, - comment_text=None, - mark_for_review=False, - mark_for_commit_queue=False, - mark_for_landing=False, - bug_id=None): - self.browser['description'] = description - self.browser['ispatch'] = ("1",) - self.browser['flag_type-1'] = ('?',) if mark_for_review else ('X',) - - if mark_for_landing: - self.browser['flag_type-3'] = ('+',) - elif mark_for_commit_queue: - self.browser['flag_type-3'] = ('?',) - else: - self.browser['flag_type-3'] = ('X',) - - if bug_id: - patch_name = "bug-%s-%s.patch" % (bug_id, timestamp()) - else: - patch_name ="%s.patch" % timestamp() - - self.browser.add_file(patch_file_object, - "text/plain", - patch_name, - 'data') - - def add_patch_to_bug(self, - bug_id, - diff, - description, - comment_text=None, - mark_for_review=False, - mark_for_commit_queue=False, - mark_for_landing=False): - self.authenticate() - - log('Adding patch "%s" to %sshow_bug.cgi?id=%s' % (description, - self.bug_server_url, - bug_id)) - - if self.dryrun: - log(comment_text) - return - - self.browser.open("%sattachment.cgi?action=enter&bugid=%s" % ( - self.bug_server_url, bug_id)) - self.browser.select_form(name="entryform") - - # _fill_attachment_form expects a file-like object - # Patch files are already binary, so no encoding needed. - assert(isinstance(diff, str)) - patch_file_object = StringIO.StringIO(diff) - self._fill_attachment_form(description, - patch_file_object, - mark_for_review=mark_for_review, - mark_for_commit_queue=mark_for_commit_queue, - mark_for_landing=mark_for_landing, - bug_id=bug_id) - if comment_text: - log(comment_text) - self.browser['comment'] = comment_text - self.browser.submit() - - def _check_create_bug_response(self, response_html): - match = re.search("<title>Bug (?P<bug_id>\d+) Submitted</title>", - response_html) - if match: - return match.group('bug_id') - - match = re.search( - '<div id="bugzilla-body">(?P<error_message>.+)<div id="footer">', - response_html, - re.DOTALL) - error_message = "FAIL" - if match: - text_lines = BeautifulSoup( - match.group('error_message')).findAll(text=True) - error_message = "\n" + '\n'.join( - [" " + line.strip() - for line in text_lines if line.strip()]) - raise Exception("Bug not created: %s" % error_message) - - def create_bug(self, - bug_title, - bug_description, - component=None, - diff=None, - patch_description=None, - cc=None, - blocked=None, - assignee=None, - mark_for_review=False, - mark_for_commit_queue=False): - self.authenticate() - - log('Creating bug with title "%s"' % bug_title) - if self.dryrun: - log(bug_description) - return - - self.browser.open(self.bug_server_url + "enter_bug.cgi?product=WebKit") - self.browser.select_form(name="Create") - component_items = self.browser.find_control('component').items - component_names = map(lambda item: item.name, component_items) - if not component: - component = "New Bugs" - if component not in component_names: - component = User.prompt_with_list("Please pick a component:", component_names) - self.browser["component"] = [component] - if cc: - self.browser["cc"] = cc - if blocked: - self.browser["blocked"] = unicode(blocked) - if assignee == None: - assignee = self.username - if assignee and not self.browser.find_control("assigned_to").disabled: - self.browser["assigned_to"] = assignee - self.browser["short_desc"] = bug_title - self.browser["comment"] = bug_description - - if diff: - # _fill_attachment_form expects a file-like object - # Patch files are already binary, so no encoding needed. - assert(isinstance(diff, str)) - patch_file_object = StringIO.StringIO(diff) - self._fill_attachment_form( - patch_description, - patch_file_object, - mark_for_review=mark_for_review, - mark_for_commit_queue=mark_for_commit_queue) - - response = self.browser.submit() - - bug_id = self._check_create_bug_response(response.read()) - log("Bug %s created." % bug_id) - log("%sshow_bug.cgi?id=%s" % (self.bug_server_url, bug_id)) - return bug_id - - def _find_select_element_for_flag(self, flag_name): - # FIXME: This will break if we ever re-order attachment flags - if flag_name == "review": - return self.browser.find_control(type='select', nr=0) - elif flag_name == "commit-queue": - return self.browser.find_control(type='select', nr=1) - raise Exception("Don't know how to find flag named \"%s\"" % flag_name) - - def clear_attachment_flags(self, - attachment_id, - additional_comment_text=None): - self.authenticate() - - comment_text = "Clearing flags on attachment: %s" % attachment_id - if additional_comment_text: - comment_text += "\n\n%s" % additional_comment_text - log(comment_text) - - if self.dryrun: - return - - self.browser.open(self.attachment_url_for_id(attachment_id, 'edit')) - self.browser.select_form(nr=1) - self.browser.set_value(comment_text, name='comment', nr=0) - self._find_select_element_for_flag('review').value = ("X",) - self._find_select_element_for_flag('commit-queue').value = ("X",) - self.browser.submit() - - def set_flag_on_attachment(self, - attachment_id, - flag_name, - flag_value, - comment_text=None, - additional_comment_text=None): - # FIXME: We need a way to test this function on a live bugzilla - # instance. - - self.authenticate() - - if additional_comment_text: - comment_text += "\n\n%s" % additional_comment_text - log(comment_text) - - if self.dryrun: - return - - self.browser.open(self.attachment_url_for_id(attachment_id, 'edit')) - self.browser.select_form(nr=1) - - if comment_text: - self.browser.set_value(comment_text, name='comment', nr=0) - - self._find_select_element_for_flag(flag_name).value = (flag_value,) - self.browser.submit() - - # FIXME: All of these bug editing methods have a ridiculous amount of - # copy/paste code. - - def obsolete_attachment(self, attachment_id, comment_text=None): - self.authenticate() - - log("Obsoleting attachment: %s" % attachment_id) - if self.dryrun: - log(comment_text) - return - - self.browser.open(self.attachment_url_for_id(attachment_id, 'edit')) - self.browser.select_form(nr=1) - self.browser.find_control('isobsolete').items[0].selected = True - # Also clear any review flag (to remove it from review/commit queues) - self._find_select_element_for_flag('review').value = ("X",) - self._find_select_element_for_flag('commit-queue').value = ("X",) - if comment_text: - log(comment_text) - # Bugzilla has two textareas named 'comment', one is somehow - # hidden. We want the first. - self.browser.set_value(comment_text, name='comment', nr=0) - self.browser.submit() - - def add_cc_to_bug(self, bug_id, email_address_list): - self.authenticate() - - log("Adding %s to the CC list for bug %s" % (email_address_list, - bug_id)) - if self.dryrun: - return - - self.browser.open(self.bug_url_for_bug_id(bug_id)) - self.browser.select_form(name="changeform") - self.browser["newcc"] = ", ".join(email_address_list) - self.browser.submit() - - def post_comment_to_bug(self, bug_id, comment_text, cc=None): - self.authenticate() - - log("Adding comment to bug %s" % bug_id) - if self.dryrun: - log(comment_text) - return - - self.browser.open(self.bug_url_for_bug_id(bug_id)) - self.browser.select_form(name="changeform") - self.browser["comment"] = comment_text - if cc: - self.browser["newcc"] = ", ".join(cc) - self.browser.submit() - - def close_bug_as_fixed(self, bug_id, comment_text=None): - self.authenticate() - - log("Closing bug %s as fixed" % bug_id) - if self.dryrun: - log(comment_text) - return - - self.browser.open(self.bug_url_for_bug_id(bug_id)) - self.browser.select_form(name="changeform") - if comment_text: - self.browser['comment'] = comment_text - self.browser['bug_status'] = ['RESOLVED'] - self.browser['resolution'] = ['FIXED'] - self.browser.submit() - - def reassign_bug(self, bug_id, assignee, comment_text=None): - self.authenticate() - - log("Assigning bug %s to %s" % (bug_id, assignee)) - if self.dryrun: - log(comment_text) - return - - self.browser.open(self.bug_url_for_bug_id(bug_id)) - self.browser.select_form(name="changeform") - if comment_text: - log(comment_text) - self.browser["comment"] = comment_text - self.browser["assigned_to"] = assignee - self.browser.submit() - - def reopen_bug(self, bug_id, comment_text): - self.authenticate() - - log("Re-opening bug %s" % bug_id) - # Bugzilla requires a comment when re-opening a bug, so we know it will - # never be None. - log(comment_text) - if self.dryrun: - return - - self.browser.open(self.bug_url_for_bug_id(bug_id)) - self.browser.select_form(name="changeform") - bug_status = self.browser.find_control("bug_status", type="select") - # This is a hack around the fact that ClientForm.ListControl seems to - # have no simpler way to ask if a control has an item named "REOPENED" - # without using exceptions for control flow. - possible_bug_statuses = map(lambda item: item.name, bug_status.items) - if "REOPENED" in possible_bug_statuses: - bug_status.value = ["REOPENED"] - # If the bug was never confirmed it will not have a "REOPENED" - # state, but only an "UNCONFIRMED" state. - elif "UNCONFIRMED" in possible_bug_statuses: - bug_status.value = ["UNCONFIRMED"] - else: - # FIXME: This logic is slightly backwards. We won't print this - # message if the bug is already open with state "UNCONFIRMED". - log("Did not reopen bug %s, it appears to already be open with status %s." % (bug_id, bug_status.value)) - self.browser['comment'] = comment_text - self.browser.submit() diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py deleted file mode 100644 index c476c81..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py +++ /dev/null @@ -1,342 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest -import datetime - -from .bugzilla import Bugzilla, BugzillaQueries, parse_bug_id - -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.tool.mocktool import MockBrowser -from webkitpy.thirdparty.mock import Mock -from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup - - -class BugzillaTest(unittest.TestCase): - _example_attachment = ''' - <attachment - isobsolete="1" - ispatch="1" - isprivate="0" - > - <attachid>33721</attachid> - <date>2009-07-29 10:23 PDT</date> - <desc>Fixed whitespace issue</desc> - <filename>patch</filename> - <type>text/plain</type> - <size>9719</size> - <attacher>christian.plesner.hansen@gmail.com</attacher> - <flag name="review" - id="17931" - status="+" - setter="one@test.com" - /> - <flag name="commit-queue" - id="17932" - status="+" - setter="two@test.com" - /> - </attachment> -''' - _expected_example_attachment_parsing = { - 'attach_date': datetime.datetime(2009, 07, 29, 10, 23), - 'bug_id' : 100, - 'is_obsolete' : True, - 'is_patch' : True, - 'id' : 33721, - 'url' : "https://bugs.webkit.org/attachment.cgi?id=33721", - 'name' : "Fixed whitespace issue", - 'type' : "text/plain", - 'review' : '+', - 'reviewer_email' : 'one@test.com', - 'commit-queue' : '+', - 'committer_email' : 'two@test.com', - 'attacher_email' : 'christian.plesner.hansen@gmail.com', - } - - def test_url_creation(self): - # FIXME: These would be all better as doctests - bugs = Bugzilla() - self.assertEquals(None, bugs.bug_url_for_bug_id(None)) - self.assertEquals(None, bugs.short_bug_url_for_bug_id(None)) - self.assertEquals(None, bugs.attachment_url_for_id(None)) - - def test_parse_bug_id(self): - # FIXME: These would be all better as doctests - bugs = Bugzilla() - self.assertEquals(12345, parse_bug_id("http://webkit.org/b/12345")) - self.assertEquals(12345, parse_bug_id("http://bugs.webkit.org/show_bug.cgi?id=12345")) - self.assertEquals(12345, parse_bug_id(bugs.short_bug_url_for_bug_id(12345))) - self.assertEquals(12345, parse_bug_id(bugs.bug_url_for_bug_id(12345))) - self.assertEquals(12345, parse_bug_id(bugs.bug_url_for_bug_id(12345, xml=True))) - - # Our bug parser is super-fragile, but at least we're testing it. - self.assertEquals(None, parse_bug_id("http://www.webkit.org/b/12345")) - self.assertEquals(None, parse_bug_id("http://bugs.webkit.org/show_bug.cgi?ctype=xml&id=12345")) - - _example_bug = """ -<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> -<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/bugzilla.dtd"> -<bugzilla version="3.2.3" - urlbase="https://bugs.webkit.org/" - maintainer="admin@webkit.org" - exporter="eric@webkit.org" -> - <bug> - <bug_id>32585</bug_id> - <creation_ts>2009-12-15 15:17 PST</creation_ts> - <short_desc>bug to test webkit-patch and commit-queue failures</short_desc> - <delta_ts>2009-12-27 21:04:50 PST</delta_ts> - <reporter_accessible>1</reporter_accessible> - <cclist_accessible>1</cclist_accessible> - <classification_id>1</classification_id> - <classification>Unclassified</classification> - <product>WebKit</product> - <component>Tools / Tests</component> - <version>528+ (Nightly build)</version> - <rep_platform>PC</rep_platform> - <op_sys>Mac OS X 10.5</op_sys> - <bug_status>NEW</bug_status> - <priority>P2</priority> - <bug_severity>Normal</bug_severity> - <target_milestone>---</target_milestone> - <everconfirmed>1</everconfirmed> - <reporter name="Eric Seidel">eric@webkit.org</reporter> - <assigned_to name="Nobody">webkit-unassigned@lists.webkit.org</assigned_to> - <cc>foo@bar.com</cc> - <cc>example@example.com</cc> - <long_desc isprivate="0"> - <who name="Eric Seidel">eric@webkit.org</who> - <bug_when>2009-12-15 15:17:28 PST</bug_when> - <thetext>bug to test webkit-patch and commit-queue failures - -Ignore this bug. Just for testing failure modes of webkit-patch and the commit-queue.</thetext> - </long_desc> - <attachment - isobsolete="0" - ispatch="1" - isprivate="0" - > - <attachid>45548</attachid> - <date>2009-12-27 23:51 PST</date> - <desc>Patch</desc> - <filename>bug-32585-20091228005112.patch</filename> - <type>text/plain</type> - <size>10882</size> - <attacher>mjs@apple.com</attacher> - - <token>1261988248-dc51409e9c421a4358f365fa8bec8357</token> - <data encoding="base64">SW5kZXg6IFdlYktpdC9tYWMvQ2hhbmdlTG9nCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09 -removed-because-it-was-really-long -ZEZpbmlzaExvYWRXaXRoUmVhc29uOnJlYXNvbl07Cit9CisKIEBlbmQKIAogI2VuZGlmCg== -</data> - - <flag name="review" - id="27602" - status="?" - setter="mjs@apple.com" - /> - </attachment> - </bug> -</bugzilla> -""" - _expected_example_bug_parsing = { - "id" : 32585, - "title" : u"bug to test webkit-patch and commit-queue failures", - "cc_emails" : ["foo@bar.com", "example@example.com"], - "reporter_email" : "eric@webkit.org", - "assigned_to_email" : "webkit-unassigned@lists.webkit.org", - "bug_status": "NEW", - "attachments" : [{ - "attach_date": datetime.datetime(2009, 12, 27, 23, 51), - 'name': u'Patch', - 'url' : "https://bugs.webkit.org/attachment.cgi?id=45548", - 'is_obsolete': False, - 'review': '?', - 'is_patch': True, - 'attacher_email': 'mjs@apple.com', - 'bug_id': 32585, - 'type': 'text/plain', - 'id': 45548 - }], - } - - # FIXME: This should move to a central location and be shared by more unit tests. - def _assert_dictionaries_equal(self, actual, expected): - # Make sure we aren't parsing more or less than we expect - self.assertEquals(sorted(actual.keys()), sorted(expected.keys())) - - for key, expected_value in expected.items(): - self.assertEquals(actual[key], expected_value, ("Failure for key: %s: Actual='%s' Expected='%s'" % (key, actual[key], expected_value))) - - def test_bug_parsing(self): - bug = Bugzilla()._parse_bug_page(self._example_bug) - self._assert_dictionaries_equal(bug, self._expected_example_bug_parsing) - - # This could be combined into test_bug_parsing later if desired. - def test_attachment_parsing(self): - bugzilla = Bugzilla() - soup = BeautifulSoup(self._example_attachment) - attachment_element = soup.find("attachment") - attachment = bugzilla._parse_attachment_element(attachment_element, self._expected_example_attachment_parsing['bug_id']) - self.assertTrue(attachment) - self._assert_dictionaries_equal(attachment, self._expected_example_attachment_parsing) - - _sample_attachment_detail_page = """ -<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" - "http://www.w3.org/TR/html4/loose.dtd"> -<html> - <head> - <title> - Attachment 41073 Details for Bug 27314</title> -<link rel="Top" href="https://bugs.webkit.org/"> - <link rel="Up" href="show_bug.cgi?id=27314"> -""" - - def test_attachment_detail_bug_parsing(self): - bugzilla = Bugzilla() - self.assertEquals(27314, bugzilla._parse_bug_id_from_attachment_page(self._sample_attachment_detail_page)) - - def test_add_cc_to_bug(self): - bugzilla = Bugzilla() - bugzilla.browser = MockBrowser() - bugzilla.authenticate = lambda: None - expected_stderr = "Adding ['adam@example.com'] to the CC list for bug 42\n" - OutputCapture().assert_outputs(self, bugzilla.add_cc_to_bug, [42, ["adam@example.com"]], expected_stderr=expected_stderr) - - def _mock_control_item(self, name): - mock_item = Mock() - mock_item.name = name - return mock_item - - def _mock_find_control(self, item_names=[], selected_index=0): - mock_control = Mock() - mock_control.items = [self._mock_control_item(name) for name in item_names] - mock_control.value = [item_names[selected_index]] if item_names else None - return lambda name, type: mock_control - - def _assert_reopen(self, item_names=None, selected_index=None, extra_stderr=None): - bugzilla = Bugzilla() - bugzilla.browser = MockBrowser() - bugzilla.authenticate = lambda: None - - mock_find_control = self._mock_find_control(item_names, selected_index) - bugzilla.browser.find_control = mock_find_control - expected_stderr = "Re-opening bug 42\n['comment']\n" - if extra_stderr: - expected_stderr += extra_stderr - OutputCapture().assert_outputs(self, bugzilla.reopen_bug, [42, ["comment"]], expected_stderr=expected_stderr) - - def test_reopen_bug(self): - self._assert_reopen(item_names=["REOPENED", "RESOLVED", "CLOSED"], selected_index=1) - self._assert_reopen(item_names=["UNCONFIRMED", "RESOLVED", "CLOSED"], selected_index=1) - extra_stderr = "Did not reopen bug 42, it appears to already be open with status ['NEW'].\n" - self._assert_reopen(item_names=["NEW", "RESOLVED"], selected_index=0, extra_stderr=extra_stderr) - - -class BugzillaQueriesTest(unittest.TestCase): - _sample_request_page = """ -<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" - "http://www.w3.org/TR/html4/loose.dtd"> -<html> - <head> - <title>Request Queue</title> - </head> -<body> - -<h3>Flag: review</h3> - <table class="requests" cellspacing="0" cellpadding="4" border="1"> - <tr> - <th>Requester</th> - <th>Requestee</th> - <th>Bug</th> - <th>Attachment</th> - <th>Created</th> - </tr> - <tr> - <td>Shinichiro Hamaji <hamaji@chromium.org></td> - <td></td> - <td><a href="show_bug.cgi?id=30015">30015: text-transform:capitalize is failing in CSS2.1 test suite</a></td> - <td><a href="attachment.cgi?id=40511&action=review"> -40511: Patch v0</a></td> - <td>2009-10-02 04:58 PST</td> - </tr> - <tr> - <td>Zan Dobersek <zandobersek@gmail.com></td> - <td></td> - <td><a href="show_bug.cgi?id=26304">26304: [GTK] Add controls for playing html5 video.</a></td> - <td><a href="attachment.cgi?id=40722&action=review"> -40722: Media controls, the simple approach</a></td> - <td>2009-10-06 09:13 PST</td> - </tr> - <tr> - <td>Zan Dobersek <zandobersek@gmail.com></td> - <td></td> - <td><a href="show_bug.cgi?id=26304">26304: [GTK] Add controls for playing html5 video.</a></td> - <td><a href="attachment.cgi?id=40723&action=review"> -40723: Adjust the media slider thumb size</a></td> - <td>2009-10-06 09:15 PST</td> - </tr> - </table> -</body> -</html> -""" - _sample_quip_page = u""" -<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" - "http://www.w3.org/TR/html4/loose.dtd"> -<html> - <head> - <title>Bugzilla Quip System</title> - </head> - <body> - <h2> - - Existing quips: - </h2> - <ul> - <li>Everything should be made as simple as possible, but not simpler. - Albert Einstein</li> - <li>Good artists copy. Great artists steal. - Pablo Picasso</li> - <li>\u00e7gua mole em pedra dura, tanto bate at\u008e que fura.</li> - - </ul> - </body> -</html> -""" - - def test_request_page_parsing(self): - queries = BugzillaQueries(None) - self.assertEquals([40511, 40722, 40723], queries._parse_attachment_ids_request_query(self._sample_request_page)) - - def test_quip_page_parsing(self): - queries = BugzillaQueries(None) - expected_quips = ["Everything should be made as simple as possible, but not simpler. - Albert Einstein", "Good artists copy. Great artists steal. - Pablo Picasso", u"\u00e7gua mole em pedra dura, tanto bate at\u008e que fura."] - self.assertEquals(expected_quips, queries._parse_quips(self._sample_quip_page)) - - def test_load_query(self): - queries = BugzillaQueries(Mock()) - queries._load_query("request.cgi?action=queue&type=review&group=type") diff --git a/WebKitTools/Scripts/webkitpy/common/net/buildbot.py b/WebKitTools/Scripts/webkitpy/common/net/buildbot.py deleted file mode 100644 index 88cdd4e..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/buildbot.py +++ /dev/null @@ -1,452 +0,0 @@ -# Copyright (c) 2009, Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# WebKit's Python module for interacting with WebKit's buildbot - -import operator -import re -import urllib -import urllib2 -import xmlrpclib - -from webkitpy.common.net.failuremap import FailureMap -from webkitpy.common.net.layouttestresults import LayoutTestResults -from webkitpy.common.net.regressionwindow import RegressionWindow -from webkitpy.common.system.logutils import get_logger -from webkitpy.thirdparty.autoinstalled.mechanize import Browser -from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup - -_log = get_logger(__file__) - - -class Builder(object): - def __init__(self, name, buildbot): - self._name = name - self._buildbot = buildbot - self._builds_cache = {} - self._revision_to_build_number = None - self._browser = Browser() - self._browser.set_handle_robots(False) # The builder pages are excluded by robots.txt - - def name(self): - return self._name - - def results_url(self): - return "http://%s/results/%s" % (self._buildbot.buildbot_host, self.url_encoded_name()) - - def url_encoded_name(self): - return urllib.quote(self._name) - - def url(self): - return "http://%s/builders/%s" % (self._buildbot.buildbot_host, self.url_encoded_name()) - - # This provides a single place to mock - def _fetch_build(self, build_number): - build_dictionary = self._buildbot._fetch_xmlrpc_build_dictionary(self, build_number) - if not build_dictionary: - return None - return Build(self, - build_number=int(build_dictionary['number']), - revision=int(build_dictionary['revision']), - is_green=(build_dictionary['results'] == 0) # Undocumented, buildbot XMLRPC, 0 seems to mean "pass" - ) - - def build(self, build_number): - if not build_number: - return None - cached_build = self._builds_cache.get(build_number) - if cached_build: - return cached_build - - build = self._fetch_build(build_number) - self._builds_cache[build_number] = build - return build - - def force_build(self, username="webkit-patch", comments=None): - def predicate(form): - try: - return form.find_control("username") - except Exception, e: - return False - self._browser.open(self.url()) - self._browser.select_form(predicate=predicate) - self._browser["username"] = username - if comments: - self._browser["comments"] = comments - return self._browser.submit() - - file_name_regexp = re.compile(r"r(?P<revision>\d+) \((?P<build_number>\d+)\)") - def _revision_and_build_for_filename(self, filename): - # Example: "r47483 (1)/" or "r47483 (1).zip" - match = self.file_name_regexp.match(filename) - return (int(match.group("revision")), int(match.group("build_number"))) - - def _fetch_revision_to_build_map(self): - # All _fetch requests go through _buildbot for easier mocking - # FIXME: This should use NetworkTransaction's 404 handling instead. - try: - # FIXME: This method is horribly slow due to the huge network load. - # FIXME: This is a poor way to do revision -> build mapping. - # Better would be to ask buildbot through some sort of API. - print "Loading revision/build list from %s." % self.results_url() - print "This may take a while..." - result_files = self._buildbot._fetch_twisted_directory_listing(self.results_url()) - except urllib2.HTTPError, error: - if error.code != 404: - raise - result_files = [] - - # This assumes there was only one build per revision, which is false but we don't care for now. - return dict([self._revision_and_build_for_filename(file_info["filename"]) for file_info in result_files]) - - def _revision_to_build_map(self): - if not self._revision_to_build_number: - self._revision_to_build_number = self._fetch_revision_to_build_map() - return self._revision_to_build_number - - def revision_build_pairs_with_results(self): - return self._revision_to_build_map().items() - - # This assumes there can be only one build per revision, which is false, but we don't care for now. - def build_for_revision(self, revision, allow_failed_lookups=False): - # NOTE: This lookup will fail if that exact revision was never built. - build_number = self._revision_to_build_map().get(int(revision)) - if not build_number: - return None - build = self.build(build_number) - if not build and allow_failed_lookups: - # Builds for old revisions with fail to lookup via buildbot's xmlrpc api. - build = Build(self, - build_number=build_number, - revision=revision, - is_green=False, - ) - return build - - def find_regression_window(self, red_build, look_back_limit=30): - if not red_build or red_build.is_green(): - return RegressionWindow(None, None) - common_failures = None - current_build = red_build - build_after_current_build = None - look_back_count = 0 - while current_build: - if current_build.is_green(): - # current_build can't possibly have any failures in common - # with red_build because it's green. - break - results = current_build.layout_test_results() - # We treat a lack of results as if all the test failed. - # This occurs, for example, when we can't compile at all. - if results: - failures = set(results.failing_tests()) - if common_failures == None: - common_failures = failures - else: - common_failures = common_failures.intersection(failures) - if not common_failures: - # current_build doesn't have any failures in common with - # the red build we're worried about. We assume that any - # failures in current_build were due to flakiness. - break - look_back_count += 1 - if look_back_count > look_back_limit: - return RegressionWindow(None, current_build, failing_tests=common_failures) - build_after_current_build = current_build - current_build = current_build.previous_build() - # We must iterate at least once because red_build is red. - assert(build_after_current_build) - # Current build must either be green or have no failures in common - # with red build, so we've found our failure transition. - return RegressionWindow(current_build, build_after_current_build, failing_tests=common_failures) - - def find_blameworthy_regression_window(self, red_build_number, look_back_limit=30, avoid_flakey_tests=True): - red_build = self.build(red_build_number) - regression_window = self.find_regression_window(red_build, look_back_limit) - if not regression_window.build_before_failure(): - return None # We ran off the limit of our search - # If avoid_flakey_tests, require at least 2 bad builds before we - # suspect a real failure transition. - if avoid_flakey_tests and regression_window.failing_build() == red_build: - return None - return regression_window - - -class Build(object): - def __init__(self, builder, build_number, revision, is_green): - self._builder = builder - self._number = build_number - self._revision = revision - self._is_green = is_green - self._layout_test_results = None - - @staticmethod - def build_url(builder, build_number): - return "%s/builds/%s" % (builder.url(), build_number) - - def url(self): - return self.build_url(self.builder(), self._number) - - def results_url(self): - results_directory = "r%s (%s)" % (self.revision(), self._number) - return "%s/%s" % (self._builder.results_url(), urllib.quote(results_directory)) - - def _fetch_results_html(self): - results_html = "%s/results.html" % (self.results_url()) - # FIXME: This should use NetworkTransaction's 404 handling instead. - try: - # It seems this can return None if the url redirects and then returns 404. - return urllib2.urlopen(results_html) - except urllib2.HTTPError, error: - if error.code != 404: - raise - - def layout_test_results(self): - if not self._layout_test_results: - # FIXME: This should cache that the result was a 404 and stop hitting the network. - self._layout_test_results = LayoutTestResults.results_from_string(self._fetch_results_html()) - return self._layout_test_results - - def builder(self): - return self._builder - - def revision(self): - return self._revision - - def is_green(self): - return self._is_green - - def previous_build(self): - # previous_build() allows callers to avoid assuming build numbers are sequential. - # They may not be sequential across all master changes, or when non-trunk builds are made. - return self._builder.build(self._number - 1) - - -class BuildBot(object): - # FIXME: This should move into some sort of webkit_config.py - default_host = "build.webkit.org" - - def __init__(self, host=default_host): - self.buildbot_host = host - self._builder_by_name = {} - - # If any core builder is red we should not be landing patches. Other - # builders should be added to this list once they are known to be - # reliable. - # See https://bugs.webkit.org/show_bug.cgi?id=33296 and related bugs. - self.core_builder_names_regexps = [ - "SnowLeopard.*Build", - "SnowLeopard.*\(Test", # Exclude WebKit2 for now. - "Leopard", - "Tiger", - "Windows.*Build", - "GTK.*32", - "GTK.*64.*Debug", # Disallow the 64-bit Release bot which is broken. - "Qt", - "Chromium.*Release$", - ] - - def _parse_last_build_cell(self, builder, cell): - status_link = cell.find('a') - if status_link: - # Will be either a revision number or a build number - revision_string = status_link.string - # If revision_string has non-digits assume it's not a revision number. - builder['built_revision'] = int(revision_string) \ - if not re.match('\D', revision_string) \ - else None - - # FIXME: We treat slave lost as green even though it is not to - # work around the Qts bot being on a broken internet connection. - # The real fix is https://bugs.webkit.org/show_bug.cgi?id=37099 - builder['is_green'] = not re.search('fail', cell.renderContents()) or \ - not not re.search('lost', cell.renderContents()) - - status_link_regexp = r"builders/(?P<builder_name>.*)/builds/(?P<build_number>\d+)" - link_match = re.match(status_link_regexp, status_link['href']) - builder['build_number'] = int(link_match.group("build_number")) - else: - # We failed to find a link in the first cell, just give up. This - # can happen if a builder is just-added, the first cell will just - # be "no build" - # Other parts of the code depend on is_green being present. - builder['is_green'] = False - builder['built_revision'] = None - builder['build_number'] = None - - def _parse_current_build_cell(self, builder, cell): - activity_lines = cell.renderContents().split("<br />") - builder["activity"] = activity_lines[0] # normally "building" or "idle" - # The middle lines document how long left for any current builds. - match = re.match("(?P<pending_builds>\d) pending", activity_lines[-1]) - builder["pending_builds"] = int(match.group("pending_builds")) if match else 0 - - def _parse_builder_status_from_row(self, status_row): - status_cells = status_row.findAll('td') - builder = {} - - # First cell is the name - name_link = status_cells[0].find('a') - builder["name"] = unicode(name_link.string) - - self._parse_last_build_cell(builder, status_cells[1]) - self._parse_current_build_cell(builder, status_cells[2]) - return builder - - def _matches_regexps(self, builder_name, name_regexps): - for name_regexp in name_regexps: - if re.match(name_regexp, builder_name): - return True - return False - - # FIXME: Should move onto Builder - def _is_core_builder(self, builder_name): - return self._matches_regexps(builder_name, self.core_builder_names_regexps) - - # FIXME: This method needs to die, but is used by a unit test at the moment. - def _builder_statuses_with_names_matching_regexps(self, builder_statuses, name_regexps): - return [builder for builder in builder_statuses if self._matches_regexps(builder["name"], name_regexps)] - - def red_core_builders(self): - return [builder for builder in self.core_builder_statuses() if not builder["is_green"]] - - def red_core_builders_names(self): - return [builder["name"] for builder in self.red_core_builders()] - - def idle_red_core_builders(self): - return [builder for builder in self.red_core_builders() if builder["activity"] == "idle"] - - def core_builders_are_green(self): - return not self.red_core_builders() - - # FIXME: These _fetch methods should move to a networking class. - def _fetch_xmlrpc_build_dictionary(self, builder, build_number): - # The buildbot XMLRPC API is super-limited. - # For one, you cannot fetch info on builds which are incomplete. - proxy = xmlrpclib.ServerProxy("http://%s/xmlrpc" % self.buildbot_host, allow_none=True) - try: - return proxy.getBuild(builder.name(), int(build_number)) - except xmlrpclib.Fault, err: - build_url = Build.build_url(builder, build_number) - _log.error("Error fetching data for %s build %s (%s): %s" % (builder.name(), build_number, build_url, err)) - return None - - def _fetch_one_box_per_builder(self): - build_status_url = "http://%s/one_box_per_builder" % self.buildbot_host - return urllib2.urlopen(build_status_url) - - def _file_cell_text(self, file_cell): - """Traverses down through firstChild elements until one containing a string is found, then returns that string""" - element = file_cell - while element.string is None and element.contents: - element = element.contents[0] - return element.string - - def _parse_twisted_file_row(self, file_row): - string_or_empty = lambda string: unicode(string) if string else u"" - file_cells = file_row.findAll('td') - return { - "filename": string_or_empty(self._file_cell_text(file_cells[0])), - "size": string_or_empty(self._file_cell_text(file_cells[1])), - "type": string_or_empty(self._file_cell_text(file_cells[2])), - "encoding": string_or_empty(self._file_cell_text(file_cells[3])), - } - - def _parse_twisted_directory_listing(self, page): - soup = BeautifulSoup(page) - # HACK: Match only table rows with a class to ignore twisted header/footer rows. - file_rows = soup.find('table').findAll('tr', {'class': re.compile(r'\b(?:directory|file)\b')}) - return [self._parse_twisted_file_row(file_row) for file_row in file_rows] - - # FIXME: There should be a better way to get this information directly from twisted. - def _fetch_twisted_directory_listing(self, url): - return self._parse_twisted_directory_listing(urllib2.urlopen(url)) - - def builders(self): - return [self.builder_with_name(status["name"]) for status in self.builder_statuses()] - - # This method pulls from /one_box_per_builder as an efficient way to get information about - def builder_statuses(self): - soup = BeautifulSoup(self._fetch_one_box_per_builder()) - return [self._parse_builder_status_from_row(status_row) for status_row in soup.find('table').findAll('tr')] - - def core_builder_statuses(self): - return [builder for builder in self.builder_statuses() if self._is_core_builder(builder["name"])] - - def builder_with_name(self, name): - builder = self._builder_by_name.get(name) - if not builder: - builder = Builder(name, self) - self._builder_by_name[name] = builder - return builder - - def failure_map(self, only_core_builders=True): - builder_statuses = self.core_builder_statuses() if only_core_builders else self.builder_statuses() - failure_map = FailureMap() - revision_to_failing_bots = {} - for builder_status in builder_statuses: - if builder_status["is_green"]: - continue - builder = self.builder_with_name(builder_status["name"]) - regression_window = builder.find_blameworthy_regression_window(builder_status["build_number"]) - if regression_window: - failure_map.add_regression_window(builder, regression_window) - return failure_map - - # This makes fewer requests than calling Builder.latest_build would. It grabs all builder - # statuses in one request using self.builder_statuses (fetching /one_box_per_builder instead of builder pages). - def _latest_builds_from_builders(self, only_core_builders=True): - builder_statuses = self.core_builder_statuses() if only_core_builders else self.builder_statuses() - return [self.builder_with_name(status["name"]).build(status["build_number"]) for status in builder_statuses] - - def _build_at_or_before_revision(self, build, revision): - while build: - if build.revision() <= revision: - return build - build = build.previous_build() - - def last_green_revision(self, only_core_builders=True): - builds = self._latest_builds_from_builders(only_core_builders) - target_revision = builds[0].revision() - # An alternate way to do this would be to start at one revision and walk backwards - # checking builder.build_for_revision, however build_for_revision is very slow on first load. - while True: - # Make builds agree on revision - builds = [self._build_at_or_before_revision(build, target_revision) for build in builds] - if None in builds: # One of the builds failed to load from the server. - return None - min_revision = min(map(lambda build: build.revision(), builds)) - if min_revision != target_revision: - target_revision = min_revision - continue # Builds don't all agree on revision, keep searching - # Check to make sure they're all green - all_are_green = reduce(operator.and_, map(lambda build: build.is_green(), builds)) - if not all_are_green: - target_revision -= 1 - continue - return min_revision diff --git a/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py deleted file mode 100644 index 4fdf24c..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py +++ /dev/null @@ -1,410 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.net.buildbot import BuildBot, Builder, Build, LayoutTestResults -from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup - - -class BuilderTest(unittest.TestCase): - def _install_fetch_build(self, failure): - def _mock_fetch_build(build_number): - build = Build( - builder=self.builder, - build_number=build_number, - revision=build_number + 1000, - is_green=build_number < 4 - ) - parsed_results = {LayoutTestResults.fail_key: failure(build_number)} - build._layout_test_results = LayoutTestResults(parsed_results) - return build - self.builder._fetch_build = _mock_fetch_build - - def setUp(self): - self.buildbot = BuildBot() - self.builder = Builder(u"Test Builder \u2661", self.buildbot) - self._install_fetch_build(lambda build_number: ["test1", "test2"]) - - def test_find_regression_window(self): - regression_window = self.builder.find_regression_window(self.builder.build(10)) - self.assertEqual(regression_window.build_before_failure().revision(), 1003) - self.assertEqual(regression_window.failing_build().revision(), 1004) - - regression_window = self.builder.find_regression_window(self.builder.build(10), look_back_limit=2) - self.assertEqual(regression_window.build_before_failure(), None) - self.assertEqual(regression_window.failing_build().revision(), 1008) - - def test_none_build(self): - self.builder._fetch_build = lambda build_number: None - regression_window = self.builder.find_regression_window(self.builder.build(10)) - self.assertEqual(regression_window.build_before_failure(), None) - self.assertEqual(regression_window.failing_build(), None) - - def test_flaky_tests(self): - self._install_fetch_build(lambda build_number: ["test1"] if build_number % 2 else ["test2"]) - regression_window = self.builder.find_regression_window(self.builder.build(10)) - self.assertEqual(regression_window.build_before_failure().revision(), 1009) - self.assertEqual(regression_window.failing_build().revision(), 1010) - - def test_failure_and_flaky(self): - self._install_fetch_build(lambda build_number: ["test1", "test2"] if build_number % 2 else ["test2"]) - regression_window = self.builder.find_regression_window(self.builder.build(10)) - self.assertEqual(regression_window.build_before_failure().revision(), 1003) - self.assertEqual(regression_window.failing_build().revision(), 1004) - - def test_no_results(self): - self._install_fetch_build(lambda build_number: ["test1", "test2"] if build_number % 2 else ["test2"]) - regression_window = self.builder.find_regression_window(self.builder.build(10)) - self.assertEqual(regression_window.build_before_failure().revision(), 1003) - self.assertEqual(regression_window.failing_build().revision(), 1004) - - def test_failure_after_flaky(self): - self._install_fetch_build(lambda build_number: ["test1", "test2"] if build_number > 6 else ["test3"]) - regression_window = self.builder.find_regression_window(self.builder.build(10)) - self.assertEqual(regression_window.build_before_failure().revision(), 1006) - self.assertEqual(regression_window.failing_build().revision(), 1007) - - def test_find_blameworthy_regression_window(self): - self.assertEqual(self.builder.find_blameworthy_regression_window(10).revisions(), [1004]) - self.assertEqual(self.builder.find_blameworthy_regression_window(10, look_back_limit=2), None) - # Flakey test avoidance requires at least 2 red builds: - self.assertEqual(self.builder.find_blameworthy_regression_window(4), None) - self.assertEqual(self.builder.find_blameworthy_regression_window(4, avoid_flakey_tests=False).revisions(), [1004]) - # Green builder: - self.assertEqual(self.builder.find_blameworthy_regression_window(3), None) - - def test_build_caching(self): - self.assertEqual(self.builder.build(10), self.builder.build(10)) - - def test_build_and_revision_for_filename(self): - expectations = { - "r47483 (1)/" : (47483, 1), - "r47483 (1).zip" : (47483, 1), - } - for filename, revision_and_build in expectations.items(): - self.assertEqual(self.builder._revision_and_build_for_filename(filename), revision_and_build) - - -class BuildTest(unittest.TestCase): - def test_layout_test_results(self): - build = Build(None, None, None, None) - build._fetch_results_html = lambda: None - # Test that layout_test_results() returns None if the fetch fails. - self.assertEqual(build.layout_test_results(), None) - - -class BuildBotTest(unittest.TestCase): - - _example_one_box_status = ''' - <table> - <tr> - <td class="box"><a href="builders/Windows%20Debug%20%28Tests%29">Windows Debug (Tests)</a></td> - <td align="center" class="LastBuild box success"><a href="builders/Windows%20Debug%20%28Tests%29/builds/3693">47380</a><br />build<br />successful</td> - <td align="center" class="Activity building">building<br />ETA in<br />~ 14 mins<br />at 13:40</td> - <tr> - <td class="box"><a href="builders/SnowLeopard%20Intel%20Release">SnowLeopard Intel Release</a></td> - <td class="LastBuild box" >no build</td> - <td align="center" class="Activity building">building<br />< 1 min</td> - <tr> - <td class="box"><a href="builders/Qt%20Linux%20Release">Qt Linux Release</a></td> - <td align="center" class="LastBuild box failure"><a href="builders/Qt%20Linux%20Release/builds/654">47383</a><br />failed<br />compile-webkit</td> - <td align="center" class="Activity idle">idle<br />3 pending</td> - <tr> - <td class="box"><a href="builders/Qt%20Windows%2032-bit%20Debug">Qt Windows 32-bit Debug</a></td> - <td align="center" class="LastBuild box failure"><a href="builders/Qt%20Windows%2032-bit%20Debug/builds/2090">60563</a><br />failed<br />failed<br />slave<br />lost</td> - <td align="center" class="Activity building">building<br />ETA in<br />~ 5 mins<br />at 08:25</td> - </table> -''' - _expected_example_one_box_parsings = [ - { - 'is_green': True, - 'build_number' : 3693, - 'name': u'Windows Debug (Tests)', - 'built_revision': 47380, - 'activity': 'building', - 'pending_builds': 0, - }, - { - 'is_green': False, - 'build_number' : None, - 'name': u'SnowLeopard Intel Release', - 'built_revision': None, - 'activity': 'building', - 'pending_builds': 0, - }, - { - 'is_green': False, - 'build_number' : 654, - 'name': u'Qt Linux Release', - 'built_revision': 47383, - 'activity': 'idle', - 'pending_builds': 3, - }, - { - 'is_green': True, - 'build_number' : 2090, - 'name': u'Qt Windows 32-bit Debug', - 'built_revision': 60563, - 'activity': 'building', - 'pending_builds': 0, - }, - ] - - def test_status_parsing(self): - buildbot = BuildBot() - - soup = BeautifulSoup(self._example_one_box_status) - status_table = soup.find("table") - input_rows = status_table.findAll('tr') - - for x in range(len(input_rows)): - status_row = input_rows[x] - expected_parsing = self._expected_example_one_box_parsings[x] - - builder = buildbot._parse_builder_status_from_row(status_row) - - # Make sure we aren't parsing more or less than we expect - self.assertEquals(builder.keys(), expected_parsing.keys()) - - for key, expected_value in expected_parsing.items(): - self.assertEquals(builder[key], expected_value, ("Builder %d parse failure for key: %s: Actual='%s' Expected='%s'" % (x, key, builder[key], expected_value))) - - def test_core_builder_methods(self): - buildbot = BuildBot() - - # Override builder_statuses function to not touch the network. - def example_builder_statuses(): # We could use instancemethod() to bind 'self' but we don't need to. - return BuildBotTest._expected_example_one_box_parsings - buildbot.builder_statuses = example_builder_statuses - - buildbot.core_builder_names_regexps = [ 'Leopard', "Windows.*Build" ] - self.assertEquals(buildbot.red_core_builders_names(), []) - self.assertTrue(buildbot.core_builders_are_green()) - - buildbot.core_builder_names_regexps = [ 'SnowLeopard', 'Qt' ] - self.assertEquals(buildbot.red_core_builders_names(), [ u'SnowLeopard Intel Release', u'Qt Linux Release' ]) - self.assertFalse(buildbot.core_builders_are_green()) - - def test_builder_name_regexps(self): - buildbot = BuildBot() - - # For complete testing, this list should match the list of builders at build.webkit.org: - example_builders = [ - {'name': u'Tiger Intel Release', }, - {'name': u'Leopard Intel Release (Build)', }, - {'name': u'Leopard Intel Release (Tests)', }, - {'name': u'Leopard Intel Debug (Build)', }, - {'name': u'Leopard Intel Debug (Tests)', }, - {'name': u'SnowLeopard Intel Release (Build)', }, - {'name': u'SnowLeopard Intel Release (Tests)', }, - {'name': u'SnowLeopard Intel Release (WebKit2 Tests)', }, - {'name': u'SnowLeopard Intel Leaks', }, - {'name': u'Windows Release (Build)', }, - {'name': u'Windows Release (Tests)', }, - {'name': u'Windows Debug (Build)', }, - {'name': u'Windows Debug (Tests)', }, - {'name': u'GTK Linux 32-bit Release', }, - {'name': u'GTK Linux 32-bit Debug', }, - {'name': u'GTK Linux 64-bit Debug', }, - {'name': u'GTK Linux 64-bit Release', }, - {'name': u'Qt Linux Release', }, - {'name': u'Qt Linux Release minimal', }, - {'name': u'Qt Linux ARMv5 Release', }, - {'name': u'Qt Linux ARMv7 Release', }, - {'name': u'Qt Windows 32-bit Release', }, - {'name': u'Qt Windows 32-bit Debug', }, - {'name': u'Chromium Linux Release', }, - {'name': u'Chromium Mac Release', }, - {'name': u'Chromium Win Release', }, - {'name': u'Chromium Linux Release (Tests)', }, - {'name': u'Chromium Mac Release (Tests)', }, - {'name': u'Chromium Win Release (Tests)', }, - {'name': u'New run-webkit-tests', }, - ] - name_regexps = [ - "SnowLeopard.*Build", - "SnowLeopard.*\(Test", - "Leopard", - "Tiger", - "Windows.*Build", - "GTK.*32", - "GTK.*64.*Debug", # Disallow the 64-bit Release bot which is broken. - "Qt", - "Chromium.*Release$", - ] - expected_builders = [ - {'name': u'Tiger Intel Release', }, - {'name': u'Leopard Intel Release (Build)', }, - {'name': u'Leopard Intel Release (Tests)', }, - {'name': u'Leopard Intel Debug (Build)', }, - {'name': u'Leopard Intel Debug (Tests)', }, - {'name': u'SnowLeopard Intel Release (Build)', }, - {'name': u'SnowLeopard Intel Release (Tests)', }, - {'name': u'Windows Release (Build)', }, - {'name': u'Windows Debug (Build)', }, - {'name': u'GTK Linux 32-bit Release', }, - {'name': u'GTK Linux 32-bit Debug', }, - {'name': u'GTK Linux 64-bit Debug', }, - {'name': u'Qt Linux Release', }, - {'name': u'Qt Linux Release minimal', }, - {'name': u'Qt Linux ARMv5 Release', }, - {'name': u'Qt Linux ARMv7 Release', }, - {'name': u'Qt Windows 32-bit Release', }, - {'name': u'Qt Windows 32-bit Debug', }, - {'name': u'Chromium Linux Release', }, - {'name': u'Chromium Mac Release', }, - {'name': u'Chromium Win Release', }, - ] - - # This test should probably be updated if the default regexp list changes - self.assertEquals(buildbot.core_builder_names_regexps, name_regexps) - - builders = buildbot._builder_statuses_with_names_matching_regexps(example_builders, name_regexps) - self.assertEquals(builders, expected_builders) - - def test_builder_with_name(self): - buildbot = BuildBot() - - builder = buildbot.builder_with_name("Test Builder") - self.assertEqual(builder.name(), "Test Builder") - self.assertEqual(builder.url(), "http://build.webkit.org/builders/Test%20Builder") - self.assertEqual(builder.url_encoded_name(), "Test%20Builder") - self.assertEqual(builder.results_url(), "http://build.webkit.org/results/Test%20Builder") - - # Override _fetch_xmlrpc_build_dictionary function to not touch the network. - def mock_fetch_xmlrpc_build_dictionary(self, build_number): - build_dictionary = { - "revision" : 2 * build_number, - "number" : int(build_number), - "results" : build_number % 2, # 0 means pass - } - return build_dictionary - buildbot._fetch_xmlrpc_build_dictionary = mock_fetch_xmlrpc_build_dictionary - - build = builder.build(10) - self.assertEqual(build.builder(), builder) - self.assertEqual(build.url(), "http://build.webkit.org/builders/Test%20Builder/builds/10") - self.assertEqual(build.results_url(), "http://build.webkit.org/results/Test%20Builder/r20%20%2810%29") - self.assertEqual(build.revision(), 20) - self.assertEqual(build.is_green(), True) - - build = build.previous_build() - self.assertEqual(build.builder(), builder) - self.assertEqual(build.url(), "http://build.webkit.org/builders/Test%20Builder/builds/9") - self.assertEqual(build.results_url(), "http://build.webkit.org/results/Test%20Builder/r18%20%289%29") - self.assertEqual(build.revision(), 18) - self.assertEqual(build.is_green(), False) - - self.assertEqual(builder.build(None), None) - - _example_directory_listing = ''' -<h1>Directory listing for /results/SnowLeopard Intel Leaks/</h1> - -<table> - <tr class="alt"> - <th>Filename</th> - <th>Size</th> - <th>Content type</th> - <th>Content encoding</th> - </tr> -<tr class="directory "> - <td><a href="r47483%20%281%29/"><b>r47483 (1)/</b></a></td> - <td><b></b></td> - <td><b>[Directory]</b></td> - <td><b></b></td> -</tr> -<tr class="file alt"> - <td><a href="r47484%20%282%29.zip">r47484 (2).zip</a></td> - <td>89K</td> - <td>[application/zip]</td> - <td></td> -</tr> -''' - _expected_files = [ - { - "filename" : "r47483 (1)/", - "size" : "", - "type" : "[Directory]", - "encoding" : "", - }, - { - "filename" : "r47484 (2).zip", - "size" : "89K", - "type" : "[application/zip]", - "encoding" : "", - }, - ] - - def test_parse_build_to_revision_map(self): - buildbot = BuildBot() - files = buildbot._parse_twisted_directory_listing(self._example_directory_listing) - self.assertEqual(self._expected_files, files) - - # Revision, is_green - # Ordered from newest (highest number) to oldest. - fake_builder1 = [ - [2, False], - [1, True], - ] - fake_builder2 = [ - [2, False], - [1, True], - ] - fake_builders = [ - fake_builder1, - fake_builder2, - ] - def _build_from_fake(self, fake_builder, index): - if index >= len(fake_builder): - return None - fake_build = fake_builder[index] - build = Build( - builder=fake_builder, - build_number=index, - revision=fake_build[0], - is_green=fake_build[1], - ) - def mock_previous_build(): - return self._build_from_fake(fake_builder, index + 1) - build.previous_build = mock_previous_build - return build - - def _fake_builds_at_index(self, index): - return [self._build_from_fake(builder, index) for builder in self.fake_builders] - - def test_last_green_revision(self): - buildbot = BuildBot() - def mock_builds_from_builders(only_core_builders): - return self._fake_builds_at_index(0) - buildbot._latest_builds_from_builders = mock_builds_from_builders - self.assertEqual(buildbot.last_green_revision(), 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/net/credentials.py b/WebKitTools/Scripts/webkitpy/common/net/credentials.py deleted file mode 100644 index 30480b3..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/credentials.py +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright (c) 2009 Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# Python module for reading stored web credentials from the OS. - -import getpass -import os -import platform -import re - -from webkitpy.common.checkout.scm import Git -from webkitpy.common.system.executive import Executive, ScriptError -from webkitpy.common.system.user import User -from webkitpy.common.system.deprecated_logging import log - -try: - # Use keyring, a cross platform keyring interface, as a fallback: - # http://pypi.python.org/pypi/keyring - import keyring -except ImportError: - keyring = None - - -class Credentials(object): - _environ_prefix = "webkit_bugzilla_" - - def __init__(self, host, git_prefix=None, executive=None, cwd=os.getcwd(), - keyring=keyring): - self.host = host - self.git_prefix = "%s." % git_prefix if git_prefix else "" - self.executive = executive or Executive() - self.cwd = cwd - self._keyring = keyring - - def _credentials_from_git(self): - try: - if not Git.in_working_directory(self.cwd): - return (None, None) - return (Git.read_git_config(self.git_prefix + "username"), - Git.read_git_config(self.git_prefix + "password")) - except OSError, e: - # Catch and ignore OSError exceptions such as "no such file - # or directory" (OSError errno 2), which imply that the Git - # command cannot be found/is not installed. - pass - return (None, None) - - def _keychain_value_with_label(self, label, source_text): - match = re.search("%s\"(?P<value>.+)\"" % label, - source_text, - re.MULTILINE) - if match: - return match.group('value') - - def _is_mac_os_x(self): - return platform.mac_ver()[0] - - def _parse_security_tool_output(self, security_output): - username = self._keychain_value_with_label("^\s*\"acct\"<blob>=", - security_output) - password = self._keychain_value_with_label("^password: ", - security_output) - return [username, password] - - def _run_security_tool(self, username=None): - security_command = [ - "/usr/bin/security", - "find-internet-password", - "-g", - "-s", - self.host, - ] - if username: - security_command += ["-a", username] - - log("Reading Keychain for %s account and password. " - "Click \"Allow\" to continue..." % self.host) - try: - return self.executive.run_command(security_command) - except ScriptError: - # Failed to either find a keychain entry or somekind of OS-related - # error occured (for instance, couldn't find the /usr/sbin/security - # command). - log("Could not find a keychain entry for %s." % self.host) - return None - - def _credentials_from_keychain(self, username=None): - if not self._is_mac_os_x(): - return [username, None] - - security_output = self._run_security_tool(username) - if security_output: - return self._parse_security_tool_output(security_output) - else: - return [None, None] - - def _read_environ(self, key): - environ_key = self._environ_prefix + key - return os.environ.get(environ_key.upper()) - - def _credentials_from_environment(self): - return (self._read_environ("username"), self._read_environ("password")) - - def _offer_to_store_credentials_in_keyring(self, username, password): - if not self._keyring: - return - if not User().confirm("Store password in system keyring?", User.DEFAULT_NO): - return - self._keyring.set_password(self.host, username, password) - - def read_credentials(self): - username, password = self._credentials_from_environment() - # FIXME: We don't currently support pulling the username from one - # source and the password from a separate source. - if not username or not password: - username, password = self._credentials_from_git() - if not username or not password: - username, password = self._credentials_from_keychain(username) - - if username and not password and self._keyring: - password = self._keyring.get_password(self.host, username) - - if not username: - username = User.prompt("%s login: " % self.host) - if not password: - password = getpass.getpass("%s password for %s: " % (self.host, username)) - self._offer_to_store_credentials_in_keyring(username, password) - - return (username, password) diff --git a/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py deleted file mode 100644 index 6f2d909..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/credentials_unittest.py +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from __future__ import with_statement - -import os -import tempfile -import unittest -from webkitpy.common.net.credentials import Credentials -from webkitpy.common.system.executive import Executive -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.thirdparty.mock import Mock - - -# FIXME: Other unit tests probably want this class. -class _TemporaryDirectory(object): - def __init__(self, **kwargs): - self._kwargs = kwargs - self._directory_path = None - - def __enter__(self): - self._directory_path = tempfile.mkdtemp(**self._kwargs) - return self._directory_path - - def __exit__(self, type, value, traceback): - os.rmdir(self._directory_path) - - -class CredentialsTest(unittest.TestCase): - example_security_output = """keychain: "/Users/test/Library/Keychains/login.keychain" -class: "inet" -attributes: - 0x00000007 <blob>="bugs.webkit.org (test@webkit.org)" - 0x00000008 <blob>=<NULL> - "acct"<blob>="test@webkit.org" - "atyp"<blob>="form" - "cdat"<timedate>=0x32303039303832353233353231365A00 "20090825235216Z\000" - "crtr"<uint32>=<NULL> - "cusi"<sint32>=<NULL> - "desc"<blob>="Web form password" - "icmt"<blob>="default" - "invi"<sint32>=<NULL> - "mdat"<timedate>=0x32303039303930393137323635315A00 "20090909172651Z\000" - "nega"<sint32>=<NULL> - "path"<blob>=<NULL> - "port"<uint32>=0x00000000 - "prot"<blob>=<NULL> - "ptcl"<uint32>="htps" - "scrp"<sint32>=<NULL> - "sdmn"<blob>=<NULL> - "srvr"<blob>="bugs.webkit.org" - "type"<uint32>=<NULL> -password: "SECRETSAUCE" -""" - - def test_keychain_lookup_on_non_mac(self): - class FakeCredentials(Credentials): - def _is_mac_os_x(self): - return False - credentials = FakeCredentials("bugs.webkit.org") - self.assertEqual(credentials._is_mac_os_x(), False) - self.assertEqual(credentials._credentials_from_keychain("foo"), ["foo", None]) - - def test_security_output_parse(self): - credentials = Credentials("bugs.webkit.org") - self.assertEqual(credentials._parse_security_tool_output(self.example_security_output), ["test@webkit.org", "SECRETSAUCE"]) - - def test_security_output_parse_entry_not_found(self): - credentials = Credentials("foo.example.com") - if not credentials._is_mac_os_x(): - return # This test does not run on a non-Mac. - - # Note, we ignore the captured output because it is already covered - # by the test case CredentialsTest._assert_security_call (below). - outputCapture = OutputCapture() - outputCapture.capture_output() - self.assertEqual(credentials._run_security_tool(), None) - outputCapture.restore_output() - - def _assert_security_call(self, username=None): - executive_mock = Mock() - credentials = Credentials("example.com", executive=executive_mock) - - expected_stderr = "Reading Keychain for example.com account and password. Click \"Allow\" to continue...\n" - OutputCapture().assert_outputs(self, credentials._run_security_tool, [username], expected_stderr=expected_stderr) - - security_args = ["/usr/bin/security", "find-internet-password", "-g", "-s", "example.com"] - if username: - security_args += ["-a", username] - executive_mock.run_command.assert_called_with(security_args) - - def test_security_calls(self): - self._assert_security_call() - self._assert_security_call(username="foo") - - def test_credentials_from_environment(self): - executive_mock = Mock() - credentials = Credentials("example.com", executive=executive_mock) - - saved_environ = os.environ.copy() - os.environ['WEBKIT_BUGZILLA_USERNAME'] = "foo" - os.environ['WEBKIT_BUGZILLA_PASSWORD'] = "bar" - username, password = credentials._credentials_from_environment() - self.assertEquals(username, "foo") - self.assertEquals(password, "bar") - os.environ = saved_environ - - def test_read_credentials_without_git_repo(self): - # FIXME: This should share more code with test_keyring_without_git_repo - class FakeCredentials(Credentials): - def _is_mac_os_x(self): - return True - - def _credentials_from_keychain(self, username): - return ("test@webkit.org", "SECRETSAUCE") - - def _credentials_from_environment(self): - return (None, None) - - with _TemporaryDirectory(suffix="not_a_git_repo") as temp_dir_path: - credentials = FakeCredentials("bugs.webkit.org", cwd=temp_dir_path) - # FIXME: Using read_credentials here seems too broad as higher-priority - # credential source could be affected by the user's environment. - self.assertEqual(credentials.read_credentials(), ("test@webkit.org", "SECRETSAUCE")) - - - def test_keyring_without_git_repo(self): - # FIXME: This should share more code with test_read_credentials_without_git_repo - class MockKeyring(object): - def get_password(self, host, username): - return "NOMNOMNOM" - - class FakeCredentials(Credentials): - def _is_mac_os_x(self): - return True - - def _credentials_from_keychain(self, username): - return ("test@webkit.org", None) - - def _credentials_from_environment(self): - return (None, None) - - with _TemporaryDirectory(suffix="not_a_git_repo") as temp_dir_path: - credentials = FakeCredentials("fake.hostname", cwd=temp_dir_path, keyring=MockKeyring()) - # FIXME: Using read_credentials here seems too broad as higher-priority - # credential source could be affected by the user's environment. - self.assertEqual(credentials.read_credentials(), ("test@webkit.org", "NOMNOMNOM")) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/net/failuremap.py b/WebKitTools/Scripts/webkitpy/common/net/failuremap.py deleted file mode 100644 index e2d53ae..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/failuremap.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -class FailureMap(object): - def __init__(self): - self._failures = [] - - def add_regression_window(self, builder, regression_window): - self._failures.append({ - 'builder': builder, - 'regression_window': regression_window, - }) - - def is_empty(self): - return not self._failures - - def failing_revisions(self): - failing_revisions = [failure_info['regression_window'].revisions() - for failure_info in self._failures] - return sorted(set(sum(failing_revisions, []))) - - def builders_failing_for(self, revision): - return self._builders_failing_because_of([revision]) - - def tests_failing_for(self, revision): - tests = [failure_info['regression_window'].failing_tests() - for failure_info in self._failures - if revision in failure_info['regression_window'].revisions() - and failure_info['regression_window'].failing_tests()] - result = set() - for test in tests: - result = result.union(test) - return sorted(result) - - def _old_failures(self, is_old_failure): - return filter(lambda revision: is_old_failure(revision), - self.failing_revisions()) - - def _builders_failing_because_of(self, revisions): - revision_set = set(revisions) - return [failure_info['builder'] for failure_info in self._failures - if revision_set.intersection( - failure_info['regression_window'].revisions())] - - # FIXME: We should re-process old failures after some time delay. - # https://bugs.webkit.org/show_bug.cgi?id=36581 - def filter_out_old_failures(self, is_old_failure): - old_failures = self._old_failures(is_old_failure) - old_failing_builder_names = set([builder.name() - for builder in self._builders_failing_because_of(old_failures)]) - - # We filter out all the failing builders that could have been caused - # by old_failures. We could miss some new failures this way, but - # emperically, this reduces the amount of spam we generate. - failures = self._failures - self._failures = [failure_info for failure_info in failures - if failure_info['builder'].name() not in old_failing_builder_names] - self._cache = {} diff --git a/WebKitTools/Scripts/webkitpy/common/net/failuremap_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/failuremap_unittest.py deleted file mode 100644 index 2f0b49d..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/failuremap_unittest.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.net.buildbot import Build -from webkitpy.common.net.failuremap import * -from webkitpy.common.net.regressionwindow import RegressionWindow -from webkitpy.tool.mocktool import MockBuilder - - -class FailureMapTest(unittest.TestCase): - builder1 = MockBuilder("Builder1") - builder2 = MockBuilder("Builder2") - - build1a = Build(builder1, build_number=22, revision=1233, is_green=True) - build1b = Build(builder1, build_number=23, revision=1234, is_green=False) - build2a = Build(builder2, build_number=89, revision=1233, is_green=True) - build2b = Build(builder2, build_number=90, revision=1235, is_green=False) - - regression_window1 = RegressionWindow(build1a, build1b, failing_tests=[u'test1', u'test1']) - regression_window2 = RegressionWindow(build2a, build2b, failing_tests=[u'test1']) - - def _make_failure_map(self): - failure_map = FailureMap() - failure_map.add_regression_window(self.builder1, self.regression_window1) - failure_map.add_regression_window(self.builder2, self.regression_window2) - return failure_map - - def test_failing_revisions(self): - failure_map = self._make_failure_map() - self.assertEquals(failure_map.failing_revisions(), [1234, 1235]) - - def test_new_failures(self): - failure_map = self._make_failure_map() - failure_map.filter_out_old_failures(lambda revision: False) - self.assertEquals(failure_map.failing_revisions(), [1234, 1235]) - - def test_new_failures_with_old_revisions(self): - failure_map = self._make_failure_map() - failure_map.filter_out_old_failures(lambda revision: revision == 1234) - self.assertEquals(failure_map.failing_revisions(), []) - - def test_new_failures_with_more_old_revisions(self): - failure_map = self._make_failure_map() - failure_map.filter_out_old_failures(lambda revision: revision == 1235) - self.assertEquals(failure_map.failing_revisions(), [1234]) - - def test_tests_failing_for(self): - failure_map = self._make_failure_map() - self.assertEquals(failure_map.tests_failing_for(1234), [u'test1']) diff --git a/WebKitTools/Scripts/webkitpy/common/net/irc/__init__.py b/WebKitTools/Scripts/webkitpy/common/net/irc/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/irc/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/common/net/irc/ircbot.py b/WebKitTools/Scripts/webkitpy/common/net/irc/ircbot.py deleted file mode 100644 index f742867..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/irc/ircbot.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import webkitpy.common.config.irc as config_irc - -from webkitpy.common.thread.messagepump import MessagePump, MessagePumpDelegate -from webkitpy.thirdparty.autoinstalled.irc import ircbot -from webkitpy.thirdparty.autoinstalled.irc import irclib - - -class IRCBotDelegate(object): - def irc_message_received(self, nick, message): - raise NotImplementedError, "subclasses must implement" - - def irc_nickname(self): - raise NotImplementedError, "subclasses must implement" - - def irc_password(self): - raise NotImplementedError, "subclasses must implement" - - -class IRCBot(ircbot.SingleServerIRCBot, MessagePumpDelegate): - # FIXME: We should get this information from a config file. - def __init__(self, - message_queue, - delegate): - self._message_queue = message_queue - self._delegate = delegate - ircbot.SingleServerIRCBot.__init__( - self, - [( - config_irc.server, - config_irc.port, - self._delegate.irc_password() - )], - self._delegate.irc_nickname(), - self._delegate.irc_nickname()) - self._channel = config_irc.channel - - # ircbot.SingleServerIRCBot methods - - def on_nicknameinuse(self, connection, event): - connection.nick(connection.get_nickname() + "_") - - def on_welcome(self, connection, event): - connection.join(self._channel) - self._message_pump = MessagePump(self, self._message_queue) - - def on_pubmsg(self, connection, event): - nick = irclib.nm_to_n(event.source()) - request = event.arguments()[0].split(":", 1) - if len(request) > 1 and irclib.irc_lower(request[0]) == irclib.irc_lower(self.connection.get_nickname()): - response = self._delegate.irc_message_received(nick, request[1]) - if response: - connection.privmsg(self._channel, response) - - # MessagePumpDelegate methods - - def schedule(self, interval, callback): - self.connection.execute_delayed(interval, callback) - - def message_available(self, message): - self.connection.privmsg(self._channel, message) - - def final_message_delivered(self): - self.die() diff --git a/WebKitTools/Scripts/webkitpy/common/net/irc/ircproxy.py b/WebKitTools/Scripts/webkitpy/common/net/irc/ircproxy.py deleted file mode 100644 index 13348b4..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/irc/ircproxy.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import threading - -from webkitpy.common.net.irc.ircbot import IRCBot -from webkitpy.common.thread.threadedmessagequeue import ThreadedMessageQueue -from webkitpy.common.system.deprecated_logging import log - - -class _IRCThread(threading.Thread): - def __init__(self, message_queue, irc_delegate, irc_bot): - threading.Thread.__init__(self) - self.setDaemon(True) - self._message_queue = message_queue - self._irc_delegate = irc_delegate - self._irc_bot = irc_bot - - def run(self): - bot = self._irc_bot(self._message_queue, self._irc_delegate) - bot.start() - - -class IRCProxy(object): - def __init__(self, irc_delegate, irc_bot=IRCBot): - log("Connecting to IRC") - self._message_queue = ThreadedMessageQueue() - self._child_thread = _IRCThread(self._message_queue, irc_delegate, irc_bot) - self._child_thread.start() - - def post(self, message): - self._message_queue.post(message) - - def disconnect(self): - log("Disconnecting from IRC...") - self._message_queue.stop() - self._child_thread.join() diff --git a/WebKitTools/Scripts/webkitpy/common/net/irc/ircproxy_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/irc/ircproxy_unittest.py deleted file mode 100644 index b44ce40..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/irc/ircproxy_unittest.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.net.irc.ircproxy import IRCProxy -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.thirdparty.mock import Mock - -class IRCProxyTest(unittest.TestCase): - def test_trivial(self): - def fun(): - proxy = IRCProxy(Mock(), Mock()) - proxy.post("hello") - proxy.disconnect() - - expected_stderr = "Connecting to IRC\nDisconnecting from IRC...\n" - OutputCapture().assert_outputs(self, fun, expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/common/net/layouttestresults.py b/WebKitTools/Scripts/webkitpy/common/net/layouttestresults.py deleted file mode 100644 index a7b3b0a..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/layouttestresults.py +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (c) 2010, Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# A module for parsing results.html files generated by old-run-webkit-tests - -from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup, SoupStrainer - - -# FIXME: This should be unified with all the layout test results code in the layout_tests package -# This doesn't belong in common.net, but we don't have a better place for it yet. -def path_for_layout_test(test_name): - return "LayoutTests/%s" % test_name - - -# FIXME: This should be unified with all the layout test results code in the layout_tests package -# This doesn't belong in common.net, but we don't have a better place for it yet. -class LayoutTestResults(object): - """This class knows how to parse old-run-webkit-tests results.html files.""" - - stderr_key = u'Tests that had stderr output:' - fail_key = u'Tests where results did not match expected results:' - timeout_key = u'Tests that timed out:' - crash_key = u'Tests that caused the DumpRenderTree tool to crash:' - missing_key = u'Tests that had no expected results (probably new):' - - expected_keys = [ - stderr_key, - fail_key, - crash_key, - timeout_key, - missing_key, - ] - - @classmethod - def _parse_results_html(cls, page): - if not page: - return None - parsed_results = {} - tables = BeautifulSoup(page).findAll("table") - for table in tables: - table_title = unicode(table.findPreviousSibling("p").string) - if table_title not in cls.expected_keys: - # This Exception should only ever be hit if run-webkit-tests changes its results.html format. - raise Exception("Unhandled title: %s" % table_title) - # We might want to translate table titles into identifiers before storing. - parsed_results[table_title] = [unicode(row.find("a").string) for row in table.findAll("tr")] - - return parsed_results - - @classmethod - def results_from_string(cls, string): - parsed_results = cls._parse_results_html(string) - if not parsed_results: - return None - return cls(parsed_results) - - def __init__(self, parsed_results): - self._parsed_results = parsed_results - - def parsed_results(self): - return self._parsed_results - - def failing_tests(self): - failing_keys = [self.fail_key, self.crash_key, self.timeout_key] - return sorted(sum([tests for key, tests in self._parsed_results.items() if key in failing_keys], [])) diff --git a/WebKitTools/Scripts/webkitpy/common/net/layouttestresults_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/layouttestresults_unittest.py deleted file mode 100644 index 8490eae..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/layouttestresults_unittest.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (c) 2010, Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.net.layouttestresults import LayoutTestResults - - -class LayoutTestResultsTest(unittest.TestCase): - _example_results_html = """ -<html> -<head> -<title>Layout Test Results</title> -</head> -<body> -<p>Tests that had stderr output:</p> -<table> -<tr> -<td><a href="/var/lib/buildbot/build/gtk-linux-64-release/build/LayoutTests/accessibility/aria-activedescendant-crash.html">accessibility/aria-activedescendant-crash.html</a></td> -<td><a href="accessibility/aria-activedescendant-crash-stderr.txt">stderr</a></td> -</tr> -<td><a href="/var/lib/buildbot/build/gtk-linux-64-release/build/LayoutTests/http/tests/security/canvas-remote-read-svg-image.html">http/tests/security/canvas-remote-read-svg-image.html</a></td> -<td><a href="http/tests/security/canvas-remote-read-svg-image-stderr.txt">stderr</a></td> -</tr> -</table><p>Tests that had no expected results (probably new):</p> -<table> -<tr> -<td><a href="/var/lib/buildbot/build/gtk-linux-64-release/build/LayoutTests/fast/repaint/no-caret-repaint-in-non-content-editable-element.html">fast/repaint/no-caret-repaint-in-non-content-editable-element.html</a></td> -<td><a href="fast/repaint/no-caret-repaint-in-non-content-editable-element-actual.txt">result</a></td> -</tr> -</table></body> -</html> -""" - - _expected_layout_test_results = { - 'Tests that had stderr output:': [ - 'accessibility/aria-activedescendant-crash.html', - ], - 'Tests that had no expected results (probably new):': [ - 'fast/repaint/no-caret-repaint-in-non-content-editable-element.html', - ], - } - - def test_parse_layout_test_results(self): - results = LayoutTestResults._parse_results_html(self._example_results_html) - self.assertEqual(self._expected_layout_test_results, results) - - def test_results_from_string(self): - self.assertEqual(LayoutTestResults.results_from_string(None), None) - self.assertEqual(LayoutTestResults.results_from_string(""), None) - results = LayoutTestResults.results_from_string(self._example_results_html) - self.assertEqual(len(results.failing_tests()), 0) diff --git a/WebKitTools/Scripts/webkitpy/common/net/networktransaction.py b/WebKitTools/Scripts/webkitpy/common/net/networktransaction.py deleted file mode 100644 index de19e94..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/networktransaction.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging -import time - -from webkitpy.thirdparty.autoinstalled import mechanize -from webkitpy.common.system.deprecated_logging import log - - -_log = logging.getLogger(__name__) - - -class NetworkTimeout(Exception): - pass - - -class NetworkTransaction(object): - def __init__(self, initial_backoff_seconds=10, grown_factor=1.5, timeout_seconds=(10 * 60), convert_404_to_None=False): - self._initial_backoff_seconds = initial_backoff_seconds - self._grown_factor = grown_factor - self._timeout_seconds = timeout_seconds - self._convert_404_to_None = convert_404_to_None - - def run(self, request): - self._total_sleep = 0 - self._backoff_seconds = self._initial_backoff_seconds - while True: - try: - return request() - # FIXME: We should catch urllib2.HTTPError here too. - except mechanize.HTTPError, e: - if self._convert_404_to_None and e.code == 404: - return None - self._check_for_timeout() - _log.warn("Received HTTP status %s from server. Retrying in " - "%s seconds..." % (e.code, self._backoff_seconds)) - self._sleep() - - def _check_for_timeout(self): - if self._total_sleep + self._backoff_seconds > self._timeout_seconds: - raise NetworkTimeout() - - def _sleep(self): - time.sleep(self._backoff_seconds) - self._total_sleep += self._backoff_seconds - self._backoff_seconds *= self._grown_factor diff --git a/WebKitTools/Scripts/webkitpy/common/net/networktransaction_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/networktransaction_unittest.py deleted file mode 100644 index 49aaeed..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/networktransaction_unittest.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.net.networktransaction import NetworkTransaction, NetworkTimeout -from webkitpy.common.system.logtesting import LoggingTestCase -from webkitpy.thirdparty.autoinstalled.mechanize import HTTPError - - -class NetworkTransactionTest(LoggingTestCase): - exception = Exception("Test exception") - - def test_success(self): - transaction = NetworkTransaction() - self.assertEqual(transaction.run(lambda: 42), 42) - - def _raise_exception(self): - raise self.exception - - def test_exception(self): - transaction = NetworkTransaction() - did_process_exception = False - did_throw_exception = True - try: - transaction.run(lambda: self._raise_exception()) - did_throw_exception = False - except Exception, e: - did_process_exception = True - self.assertEqual(e, self.exception) - self.assertTrue(did_throw_exception) - self.assertTrue(did_process_exception) - - def _raise_500_error(self): - self._run_count += 1 - if self._run_count < 3: - raise HTTPError("http://example.com/", 500, "internal server error", None, None) - return 42 - - def _raise_404_error(self): - raise HTTPError("http://foo.com/", 404, "not found", None, None) - - def test_retry(self): - self._run_count = 0 - transaction = NetworkTransaction(initial_backoff_seconds=0) - self.assertEqual(transaction.run(lambda: self._raise_500_error()), 42) - self.assertEqual(self._run_count, 3) - self.assertLog(['WARNING: Received HTTP status 500 from server. ' - 'Retrying in 0 seconds...\n', - 'WARNING: Received HTTP status 500 from server. ' - 'Retrying in 0.0 seconds...\n']) - - def test_convert_404_to_None(self): - transaction = NetworkTransaction(convert_404_to_None=True) - self.assertEqual(transaction.run(lambda: self._raise_404_error()), None) - - def test_timeout(self): - self._run_count = 0 - transaction = NetworkTransaction(initial_backoff_seconds=60*60, timeout_seconds=60) - did_process_exception = False - did_throw_exception = True - try: - transaction.run(lambda: self._raise_500_error()) - did_throw_exception = False - except NetworkTimeout, e: - did_process_exception = True - self.assertTrue(did_throw_exception) - self.assertTrue(did_process_exception) diff --git a/WebKitTools/Scripts/webkitpy/common/net/regressionwindow.py b/WebKitTools/Scripts/webkitpy/common/net/regressionwindow.py deleted file mode 100644 index ad89815..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/regressionwindow.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -class RegressionWindow(object): - def __init__(self, build_before_failure, failing_build, failing_tests=None): - self._build_before_failure = build_before_failure - self._failing_build = failing_build - self._failing_tests = failing_tests - self._revisions = None - - def build_before_failure(self): - return self._build_before_failure - - def failing_build(self): - return self._failing_build - - def failing_tests(self): - return self._failing_tests - - def revisions(self): - # Cache revisions to avoid excessive allocations. - if not self._revisions: - self._revisions = range(self._failing_build.revision(), self._build_before_failure.revision(), -1) - self._revisions.reverse() - return self._revisions diff --git a/WebKitTools/Scripts/webkitpy/common/net/statusserver.py b/WebKitTools/Scripts/webkitpy/common/net/statusserver.py deleted file mode 100644 index 64dd77b..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/statusserver.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.common.net.networktransaction import NetworkTransaction -from webkitpy.common.system.deprecated_logging import log -from webkitpy.thirdparty.autoinstalled.mechanize import Browser -from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup - -import logging -import urllib2 - - -_log = logging.getLogger("webkitpy.common.net.statusserver") - - -class StatusServer: - default_host = "queues.webkit.org" - - def __init__(self, host=default_host, browser=None, bot_id=None): - self.set_host(host) - self._browser = browser or Browser() - self.set_bot_id(bot_id) - - def set_host(self, host): - self.host = host - self.url = "http://%s" % self.host - - def set_bot_id(self, bot_id): - self.bot_id = bot_id - - def results_url_for_status(self, status_id): - return "%s/results/%s" % (self.url, status_id) - - def _add_patch(self, patch): - if not patch: - return - if patch.bug_id(): - self._browser["bug_id"] = unicode(patch.bug_id()) - if patch.id(): - self._browser["patch_id"] = unicode(patch.id()) - - def _add_results_file(self, results_file): - if not results_file: - return - self._browser.add_file(results_file, "text/plain", "results.txt", 'results_file') - - def _post_status_to_server(self, queue_name, status, patch, results_file): - if results_file: - # We might need to re-wind the file if we've already tried to post it. - results_file.seek(0) - - update_status_url = "%s/update-status" % self.url - self._browser.open(update_status_url) - self._browser.select_form(name="update_status") - self._browser["queue_name"] = queue_name - if self.bot_id: - self._browser["bot_id"] = self.bot_id - self._add_patch(patch) - self._browser["status"] = status - self._add_results_file(results_file) - return self._browser.submit().read() # This is the id of the newly created status object. - - def _post_svn_revision_to_server(self, svn_revision_number, broken_bot): - update_svn_revision_url = "%s/update-svn-revision" % self.url - self._browser.open(update_svn_revision_url) - self._browser.select_form(name="update_svn_revision") - self._browser["number"] = unicode(svn_revision_number) - self._browser["broken_bot"] = broken_bot - return self._browser.submit().read() - - def _post_work_items_to_server(self, queue_name, work_items): - update_work_items_url = "%s/update-work-items" % self.url - self._browser.open(update_work_items_url) - self._browser.select_form(name="update_work_items") - self._browser["queue_name"] = queue_name - work_items = map(unicode, work_items) # .join expects strings - self._browser["work_items"] = " ".join(work_items) - return self._browser.submit().read() - - def _post_work_item_to_ews(self, attachment_id): - submit_to_ews_url = "%s/submit-to-ews" % self.url - self._browser.open(submit_to_ews_url) - self._browser.select_form(name="submit_to_ews") - self._browser["attachment_id"] = unicode(attachment_id) - self._browser.submit() - - def submit_to_ews(self, attachment_id): - _log.info("Submitting attachment %s to EWS queues" % attachment_id) - return NetworkTransaction().run(lambda: self._post_work_item_to_ews(attachment_id)) - - def next_work_item(self, queue_name): - _log.debug("Fetching next work item for %s" % queue_name) - patch_status_url = "%s/next-patch/%s" % (self.url, queue_name) - return self._fetch_url(patch_status_url) - - def _post_release_work_item(self, queue_name, patch): - release_patch_url = "%s/release-patch" % (self.url) - self._browser.open(release_patch_url) - self._browser.select_form(name="release_patch") - self._browser["queue_name"] = queue_name - self._browser["attachment_id"] = unicode(patch.id()) - self._browser.submit() - - def release_work_item(self, queue_name, patch): - _log.info("Releasing work item %s from %s" % (patch.id(), queue_name)) - return NetworkTransaction(convert_404_to_None=True).run(lambda: self._post_release_work_item(queue_name, patch)) - - def update_work_items(self, queue_name, work_items): - _log.debug("Recording work items: %s for %s" % (work_items, queue_name)) - return NetworkTransaction().run(lambda: self._post_work_items_to_server(queue_name, work_items)) - - def update_status(self, queue_name, status, patch=None, results_file=None): - log(status) - return NetworkTransaction().run(lambda: self._post_status_to_server(queue_name, status, patch, results_file)) - - def update_svn_revision(self, svn_revision_number, broken_bot): - log("SVN revision: %s broke %s" % (svn_revision_number, broken_bot)) - return NetworkTransaction().run(lambda: self._post_svn_revision_to_server(svn_revision_number, broken_bot)) - - def _fetch_url(self, url): - # FIXME: This should use NetworkTransaction's 404 handling instead. - try: - return urllib2.urlopen(url).read() - except urllib2.HTTPError, e: - if e.code == 404: - return None - raise e - - def patch_status(self, queue_name, patch_id): - patch_status_url = "%s/patch-status/%s/%s" % (self.url, queue_name, patch_id) - return self._fetch_url(patch_status_url) - - def svn_revision(self, svn_revision_number): - svn_revision_url = "%s/svn-revision/%s" % (self.url, svn_revision_number) - return self._fetch_url(svn_revision_url) diff --git a/WebKitTools/Scripts/webkitpy/common/net/statusserver_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/statusserver_unittest.py deleted file mode 100644 index 1169ba0..0000000 --- a/WebKitTools/Scripts/webkitpy/common/net/statusserver_unittest.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.net.statusserver import StatusServer -from webkitpy.common.system.outputcapture import OutputCaptureTestCaseBase -from webkitpy.tool.mocktool import MockBrowser - - -class StatusServerTest(OutputCaptureTestCaseBase): - def test_url_for_issue(self): - mock_browser = MockBrowser() - status_server = StatusServer(browser=mock_browser, bot_id='123') - status_server.update_status('queue name', 'the status') - self.assertEqual('queue name', mock_browser.params['queue_name']) - self.assertEqual('the status', mock_browser.params['status']) - self.assertEqual('123', mock_browser.params['bot_id']) diff --git a/WebKitTools/Scripts/webkitpy/common/newstringio.py b/WebKitTools/Scripts/webkitpy/common/newstringio.py deleted file mode 100644 index f6d08ec..0000000 --- a/WebKitTools/Scripts/webkitpy/common/newstringio.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""'with'-compliant StringIO implementation.""" - -import StringIO - - -class StringIO(StringIO.StringIO): - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - pass diff --git a/WebKitTools/Scripts/webkitpy/common/newstringio_unittest.py b/WebKitTools/Scripts/webkitpy/common/newstringio_unittest.py deleted file mode 100644 index 5755c98..0000000 --- a/WebKitTools/Scripts/webkitpy/common/newstringio_unittest.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Unit tests for newstringio module.""" - -from __future__ import with_statement - -import unittest - -import newstringio - - -class NewStringIOTest(unittest.TestCase): - def test_with(self): - with newstringio.StringIO("foo") as f: - contents = f.read() - self.assertEqual(contents, "foo") - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/prettypatch.py b/WebKitTools/Scripts/webkitpy/common/prettypatch.py deleted file mode 100644 index 4e92a53..0000000 --- a/WebKitTools/Scripts/webkitpy/common/prettypatch.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import os -import tempfile - - -class PrettyPatch(object): - # FIXME: PrettyPatch should not require checkout_root. - def __init__(self, executive, checkout_root): - self._executive = executive - self._checkout_root = checkout_root - - def pretty_diff_file(self, diff): - # Diffs can contain multiple text files of different encodings - # so we always deal with them as byte arrays, not unicode strings. - assert(isinstance(diff, str)) - pretty_diff = self.pretty_diff(diff) - diff_file = tempfile.NamedTemporaryFile(suffix=".html") - diff_file.write(pretty_diff) - diff_file.flush() - return diff_file - - def pretty_diff(self, diff): - # pretify.rb will hang forever if given no input. - # Avoid the hang by returning an empty string. - if not diff: - return "" - - pretty_patch_path = os.path.join(self._checkout_root, - "BugsSite", "PrettyPatch") - prettify_path = os.path.join(pretty_patch_path, "prettify.rb") - args = [ - "ruby", - "-I", - pretty_patch_path, - prettify_path, - ] - # PrettyPatch does not modify the encoding of the diff output - # so we can't expect it to be utf-8. - return self._executive.run_command(args, input=diff, decode_output=False) diff --git a/WebKitTools/Scripts/webkitpy/common/prettypatch_unittest.py b/WebKitTools/Scripts/webkitpy/common/prettypatch_unittest.py deleted file mode 100644 index 1307856..0000000 --- a/WebKitTools/Scripts/webkitpy/common/prettypatch_unittest.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import os.path -import unittest - -from webkitpy.common.system.executive import Executive -from webkitpy.common.prettypatch import PrettyPatch - - -class PrettyPatchTest(unittest.TestCase): - - _diff_with_multiple_encodings = """ -Index: utf8_test -=================================================================== ---- utf8_test\t(revision 0) -+++ utf8_test\t(revision 0) -@@ -0,0 +1 @@ -+utf-8 test: \xc2\xa0 -Index: latin1_test -=================================================================== ---- latin1_test\t(revision 0) -+++ latin1_test\t(revision 0) -@@ -0,0 +1 @@ -+latin1 test: \xa0 -""" - - def _webkit_root(self): - webkitpy_common = os.path.dirname(__file__) - webkitpy = os.path.dirname(webkitpy_common) - scripts = os.path.dirname(webkitpy) - webkit_tools = os.path.dirname(scripts) - webkit_root = os.path.dirname(webkit_tools) - return webkit_root - - def test_pretty_diff_encodings(self): - pretty_patch = PrettyPatch(Executive(), self._webkit_root()) - pretty = pretty_patch.pretty_diff(self._diff_with_multiple_encodings) - self.assertTrue(pretty) # We got some output - self.assertTrue(isinstance(pretty, str)) # It's a byte array, not unicode - - def test_pretty_print_empty_string(self): - # Make sure that an empty diff does not hang the process. - pretty_patch = PrettyPatch(Executive(), self._webkit_root()) - self.assertEqual(pretty_patch.pretty_diff(""), "") diff --git a/WebKitTools/Scripts/webkitpy/common/system/__init__.py b/WebKitTools/Scripts/webkitpy/common/system/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/common/system/autoinstall.py b/WebKitTools/Scripts/webkitpy/common/system/autoinstall.py deleted file mode 100755 index 9adab29..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/autoinstall.py +++ /dev/null @@ -1,517 +0,0 @@ -# Copyright (c) 2009, Daniel Krech All rights reserved. -# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of the Daniel Krech nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Support for automatically downloading Python packages from an URL.""" - - -from __future__ import with_statement - -import codecs -import logging -import new -import os -import shutil -import sys -import tarfile -import tempfile -import urllib -import urlparse -import zipfile -import zipimport - -_log = logging.getLogger(__name__) - - -class AutoInstaller(object): - - """Supports automatically installing Python packages from an URL. - - Supports uncompressed files, .tar.gz, and .zip formats. - - Basic usage: - - installer = AutoInstaller() - - installer.install(url="http://pypi.python.org/packages/source/p/pep8/pep8-0.5.0.tar.gz#md5=512a818af9979290cd619cce8e9c2e2b", - url_subpath="pep8-0.5.0/pep8.py") - installer.install(url="http://pypi.python.org/packages/source/m/mechanize/mechanize-0.1.11.zip", - url_subpath="mechanize") - - """ - - def __init__(self, append_to_search_path=False, make_package=True, - target_dir=None, temp_dir=None): - """Create an AutoInstaller instance, and set up the target directory. - - Args: - append_to_search_path: A boolean value of whether to append the - target directory to the sys.path search path. - make_package: A boolean value of whether to make the target - directory a package. This adds an __init__.py file - to the target directory -- allowing packages and - modules within the target directory to be imported - explicitly using dotted module names. - target_dir: The directory path to which packages should be installed. - Defaults to a subdirectory of the folder containing - this module called "autoinstalled". - temp_dir: The directory path to use for any temporary files - generated while downloading, unzipping, and extracting - packages to install. Defaults to a standard temporary - location generated by the tempfile module. This - parameter should normally be used only for development - testing. - - """ - if target_dir is None: - this_dir = os.path.dirname(__file__) - target_dir = os.path.join(this_dir, "autoinstalled") - - # Ensure that the target directory exists. - self._set_up_target_dir(target_dir, append_to_search_path, make_package) - - self._target_dir = target_dir - self._temp_dir = temp_dir - - def _log_transfer(self, message, source, target, log_method=None): - """Log a debug message that involves a source and target.""" - if log_method is None: - log_method = _log.debug - - log_method("%s" % message) - log_method(' From: "%s"' % source) - log_method(' To: "%s"' % target) - - def _create_directory(self, path, name=None): - """Create a directory.""" - log = _log.debug - - name = name + " " if name is not None else "" - log('Creating %sdirectory...' % name) - log(' "%s"' % path) - - os.makedirs(path) - - def _write_file(self, path, text, encoding): - """Create a file at the given path with given text. - - This method overwrites any existing file. - - """ - _log.debug("Creating file...") - _log.debug(' "%s"' % path) - with codecs.open(path, "w", encoding) as file: - file.write(text) - - def _set_up_target_dir(self, target_dir, append_to_search_path, - make_package): - """Set up a target directory. - - Args: - target_dir: The path to the target directory to set up. - append_to_search_path: A boolean value of whether to append the - target directory to the sys.path search path. - make_package: A boolean value of whether to make the target - directory a package. This adds an __init__.py file - to the target directory -- allowing packages and - modules within the target directory to be imported - explicitly using dotted module names. - - """ - if not os.path.exists(target_dir): - self._create_directory(target_dir, "autoinstall target") - - if append_to_search_path: - sys.path.append(target_dir) - - if make_package: - init_path = os.path.join(target_dir, "__init__.py") - if not os.path.exists(init_path): - text = ("# This file is required for Python to search this " - "directory for modules.\n") - self._write_file(init_path, text, "ascii") - - def _create_scratch_directory_inner(self, prefix): - """Create a scratch directory without exception handling. - - Creates a scratch directory inside the AutoInstaller temp - directory self._temp_dir, or inside a platform-dependent temp - directory if self._temp_dir is None. Returns the path to the - created scratch directory. - - Raises: - OSError: [Errno 2] if the containing temp directory self._temp_dir - is not None and does not exist. - - """ - # The tempfile.mkdtemp() method function requires that the - # directory corresponding to the "dir" parameter already exist - # if it is not None. - scratch_dir = tempfile.mkdtemp(prefix=prefix, dir=self._temp_dir) - return scratch_dir - - def _create_scratch_directory(self, target_name): - """Create a temporary scratch directory, and return its path. - - The scratch directory is generated inside the temp directory - of this AutoInstaller instance. This method also creates the - temp directory if it does not already exist. - - """ - prefix = target_name + "_" - try: - scratch_dir = self._create_scratch_directory_inner(prefix) - except OSError: - # Handle case of containing temp directory not existing-- - # OSError: [Errno 2] No such file or directory:... - temp_dir = self._temp_dir - if temp_dir is None or os.path.exists(temp_dir): - raise - # Else try again after creating the temp directory. - self._create_directory(temp_dir, "autoinstall temp") - scratch_dir = self._create_scratch_directory_inner(prefix) - - return scratch_dir - - def _url_downloaded_path(self, target_name): - """Return the path to the file containing the URL downloaded.""" - filename = ".%s.url" % target_name - path = os.path.join(self._target_dir, filename) - return path - - def _is_downloaded(self, target_name, url): - """Return whether a package version has been downloaded.""" - version_path = self._url_downloaded_path(target_name) - - _log.debug('Checking %s URL downloaded...' % target_name) - _log.debug(' "%s"' % version_path) - - if not os.path.exists(version_path): - # Then no package version has been downloaded. - _log.debug("No URL file found.") - return False - - with codecs.open(version_path, "r", "utf-8") as file: - version = file.read() - - return version.strip() == url.strip() - - def _record_url_downloaded(self, target_name, url): - """Record the URL downloaded to a file.""" - version_path = self._url_downloaded_path(target_name) - _log.debug("Recording URL downloaded...") - _log.debug(' URL: "%s"' % url) - _log.debug(' To: "%s"' % version_path) - - self._write_file(version_path, url, "utf-8") - - def _extract_targz(self, path, scratch_dir): - # tarfile.extractall() extracts to a path without the - # trailing ".tar.gz". - target_basename = os.path.basename(path[:-len(".tar.gz")]) - target_path = os.path.join(scratch_dir, target_basename) - - self._log_transfer("Starting gunzip/extract...", path, target_path) - - try: - tar_file = tarfile.open(path) - except tarfile.ReadError, err: - # Append existing Error message to new Error. - message = ("Could not open tar file: %s\n" - " The file probably does not have the correct format.\n" - " --> Inner message: %s" - % (path, err)) - raise Exception(message) - - try: - # This is helpful for debugging purposes. - _log.debug("Listing tar file contents...") - for name in tar_file.getnames(): - _log.debug(' * "%s"' % name) - _log.debug("Extracting gzipped tar file...") - tar_file.extractall(target_path) - finally: - tar_file.close() - - return target_path - - # This is a replacement for ZipFile.extractall(), which is - # available in Python 2.6 but not in earlier versions. - def _extract_all(self, zip_file, target_dir): - self._log_transfer("Extracting zip file...", zip_file, target_dir) - - # This is helpful for debugging purposes. - _log.debug("Listing zip file contents...") - for name in zip_file.namelist(): - _log.debug(' * "%s"' % name) - - for name in zip_file.namelist(): - path = os.path.join(target_dir, name) - self._log_transfer("Extracting...", name, path) - - if not os.path.basename(path): - # Then the path ends in a slash, so it is a directory. - self._create_directory(path) - continue - # Otherwise, it is a file. - - try: - # We open this file w/o encoding, as we're reading/writing - # the raw byte-stream from the zip file. - outfile = open(path, 'wb') - except IOError, err: - # Not all zip files seem to list the directories explicitly, - # so try again after creating the containing directory. - _log.debug("Got IOError: retrying after creating directory...") - dir = os.path.dirname(path) - self._create_directory(dir) - outfile = open(path, 'wb') - - try: - outfile.write(zip_file.read(name)) - finally: - outfile.close() - - def _unzip(self, path, scratch_dir): - # zipfile.extractall() extracts to a path without the - # trailing ".zip". - target_basename = os.path.basename(path[:-len(".zip")]) - target_path = os.path.join(scratch_dir, target_basename) - - self._log_transfer("Starting unzip...", path, target_path) - - try: - zip_file = zipfile.ZipFile(path, "r") - except zipfile.BadZipfile, err: - message = ("Could not open zip file: %s\n" - " --> Inner message: %s" - % (path, err)) - raise Exception(message) - - try: - self._extract_all(zip_file, scratch_dir) - finally: - zip_file.close() - - return target_path - - def _prepare_package(self, path, scratch_dir): - """Prepare a package for use, if necessary, and return the new path. - - For example, this method unzips zipped files and extracts - tar files. - - Args: - path: The path to the downloaded URL contents. - scratch_dir: The scratch directory. Note that the scratch - directory contains the file designated by the - path parameter. - - """ - # FIXME: Add other natural extensions. - if path.endswith(".zip"): - new_path = self._unzip(path, scratch_dir) - elif path.endswith(".tar.gz"): - new_path = self._extract_targz(path, scratch_dir) - else: - # No preparation is needed. - new_path = path - - return new_path - - def _download_to_stream(self, url, stream): - """Download an URL to a stream, and return the number of bytes.""" - try: - netstream = urllib.urlopen(url) - except IOError, err: - # Append existing Error message to new Error. - message = ('Could not download Python modules from URL "%s".\n' - " Make sure you are connected to the internet.\n" - " You must be connected to the internet when " - "downloading needed modules for the first time.\n" - " --> Inner message: %s" - % (url, err)) - raise IOError(message) - code = 200 - if hasattr(netstream, "getcode"): - code = netstream.getcode() - if not 200 <= code < 300: - raise ValueError("HTTP Error code %s" % code) - - BUFSIZE = 2**13 # 8KB - bytes = 0 - while True: - data = netstream.read(BUFSIZE) - if not data: - break - stream.write(data) - bytes += len(data) - netstream.close() - return bytes - - def _download(self, url, scratch_dir): - """Download URL contents, and return the download path.""" - url_path = urlparse.urlsplit(url)[2] - url_path = os.path.normpath(url_path) # Removes trailing slash. - target_filename = os.path.basename(url_path) - target_path = os.path.join(scratch_dir, target_filename) - - self._log_transfer("Starting download...", url, target_path) - - with open(target_path, "wb") as stream: - bytes = self._download_to_stream(url, stream) - - _log.debug("Downloaded %s bytes." % bytes) - - return target_path - - def _install(self, scratch_dir, package_name, target_path, url, - url_subpath): - """Install a python package from an URL. - - This internal method overwrites the target path if the target - path already exists. - - """ - path = self._download(url=url, scratch_dir=scratch_dir) - path = self._prepare_package(path, scratch_dir) - - if url_subpath is None: - source_path = path - else: - source_path = os.path.join(path, url_subpath) - - if os.path.exists(target_path): - _log.debug('Refreshing install: deleting "%s".' % target_path) - if os.path.isdir(target_path): - shutil.rmtree(target_path) - else: - os.remove(target_path) - - self._log_transfer("Moving files into place...", source_path, target_path) - - # The shutil.move() command creates intermediate directories if they - # do not exist, but we do not rely on this behavior since we - # need to create the __init__.py file anyway. - shutil.move(source_path, target_path) - - self._record_url_downloaded(package_name, url) - - def install(self, url, should_refresh=False, target_name=None, - url_subpath=None): - """Install a python package from an URL. - - Args: - url: The URL from which to download the package. - - Optional Args: - should_refresh: A boolean value of whether the package should be - downloaded again if the package is already present. - target_name: The name of the folder or file in the autoinstaller - target directory at which the package should be - installed. Defaults to the base name of the - URL sub-path. This parameter must be provided if - the URL sub-path is not specified. - url_subpath: The relative path of the URL directory that should - be installed. Defaults to the full directory, or - the entire URL contents. - - """ - if target_name is None: - if not url_subpath: - raise ValueError('The "target_name" parameter must be ' - 'provided if the "url_subpath" parameter ' - "is not provided.") - # Remove any trailing slashes. - url_subpath = os.path.normpath(url_subpath) - target_name = os.path.basename(url_subpath) - - target_path = os.path.join(self._target_dir, target_name) - if not should_refresh and self._is_downloaded(target_name, url): - _log.debug('URL for %s already downloaded. Skipping...' - % target_name) - _log.debug(' "%s"' % url) - return - - self._log_transfer("Auto-installing package: %s" % target_name, - url, target_path, log_method=_log.info) - - # The scratch directory is where we will download and prepare - # files specific to this install until they are ready to move - # into place. - scratch_dir = self._create_scratch_directory(target_name) - - try: - self._install(package_name=target_name, - target_path=target_path, - scratch_dir=scratch_dir, - url=url, - url_subpath=url_subpath) - except Exception, err: - # Append existing Error message to new Error. - message = ("Error auto-installing the %s package to:\n" - ' "%s"\n' - " --> Inner message: %s" - % (target_name, target_path, err)) - raise Exception(message) - finally: - _log.debug('Cleaning up: deleting "%s".' % scratch_dir) - shutil.rmtree(scratch_dir) - _log.debug('Auto-installed %s to:' % target_name) - _log.debug(' "%s"' % target_path) - - -if __name__=="__main__": - - # Configure the autoinstall logger to log DEBUG messages for - # development testing purposes. - console = logging.StreamHandler() - - formatter = logging.Formatter('%(name)s: %(levelname)-8s %(message)s') - console.setFormatter(formatter) - _log.addHandler(console) - _log.setLevel(logging.DEBUG) - - # Use a more visible temp directory for debug purposes. - this_dir = os.path.dirname(__file__) - target_dir = os.path.join(this_dir, "autoinstalled") - temp_dir = os.path.join(target_dir, "Temp") - - installer = AutoInstaller(target_dir=target_dir, - temp_dir=temp_dir) - - installer.install(should_refresh=False, - target_name="pep8.py", - url="http://pypi.python.org/packages/source/p/pep8/pep8-0.5.0.tar.gz#md5=512a818af9979290cd619cce8e9c2e2b", - url_subpath="pep8-0.5.0/pep8.py") - installer.install(should_refresh=False, - target_name="mechanize", - url="http://pypi.python.org/packages/source/m/mechanize/mechanize-0.1.11.zip", - url_subpath="mechanize") - diff --git a/WebKitTools/Scripts/webkitpy/common/system/deprecated_logging.py b/WebKitTools/Scripts/webkitpy/common/system/deprecated_logging.py deleted file mode 100644 index 9e6b529..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/deprecated_logging.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) 2009, Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# WebKit's Python module for logging -# This module is now deprecated in favor of python's built-in logging.py. - -import codecs -import os -import sys - - -def log(string): - print >> sys.stderr, string - - -def error(string): - log("ERROR: %s" % string) - exit(1) - - -# Simple class to split output between multiple destinations -class tee: - def __init__(self, *files): - self.files = files - - # Callers should pass an already encoded string for writing. - def write(self, bytes): - for file in self.files: - file.write(bytes) - - -class OutputTee: - def __init__(self): - self._original_stdout = None - self._original_stderr = None - self._files_for_output = [] - - def add_log(self, path): - log_file = self._open_log_file(path) - self._files_for_output.append(log_file) - self._tee_outputs_to_files(self._files_for_output) - return log_file - - def remove_log(self, log_file): - self._files_for_output.remove(log_file) - self._tee_outputs_to_files(self._files_for_output) - log_file.close() - - @staticmethod - def _open_log_file(log_path): - (log_directory, log_name) = os.path.split(log_path) - if log_directory and not os.path.exists(log_directory): - os.makedirs(log_directory) - return codecs.open(log_path, "a+", "utf-8") - - def _tee_outputs_to_files(self, files): - if not self._original_stdout: - self._original_stdout = sys.stdout - self._original_stderr = sys.stderr - if files and len(files): - sys.stdout = tee(self._original_stdout, *files) - sys.stderr = tee(self._original_stderr, *files) - else: - sys.stdout = self._original_stdout - sys.stderr = self._original_stderr diff --git a/WebKitTools/Scripts/webkitpy/common/system/deprecated_logging_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/deprecated_logging_unittest.py deleted file mode 100644 index 3778162..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/deprecated_logging_unittest.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import os -import StringIO -import tempfile -import unittest - -from webkitpy.common.system.executive import ScriptError -from webkitpy.common.system.deprecated_logging import * - -class LoggingTest(unittest.TestCase): - - def assert_log_equals(self, log_input, expected_output): - original_stderr = sys.stderr - test_stderr = StringIO.StringIO() - sys.stderr = test_stderr - - try: - log(log_input) - actual_output = test_stderr.getvalue() - finally: - sys.stderr = original_stderr - - self.assertEquals(actual_output, expected_output, "log(\"%s\") expected: %s actual: %s" % (log_input, expected_output, actual_output)) - - def test_log(self): - self.assert_log_equals("test", "test\n") - - # Test that log() does not throw an exception when passed an object instead of a string. - self.assert_log_equals(ScriptError(message="ScriptError"), "ScriptError\n") - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/system/executive.py b/WebKitTools/Scripts/webkitpy/common/system/executive.py deleted file mode 100644 index 85a683a..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/executive.py +++ /dev/null @@ -1,399 +0,0 @@ -# Copyright (c) 2009, Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -try: - # This API exists only in Python 2.6 and higher. :( - import multiprocessing -except ImportError: - multiprocessing = None - -import ctypes -import errno -import logging -import os -import platform -import StringIO -import signal -import subprocess -import sys -import time - -from webkitpy.common.system.deprecated_logging import tee -from webkitpy.python24 import versioning - - -_log = logging.getLogger("webkitpy.common.system") - - -class ScriptError(Exception): - - def __init__(self, - message=None, - script_args=None, - exit_code=None, - output=None, - cwd=None): - if not message: - message = 'Failed to run "%s"' % script_args - if exit_code: - message += " exit_code: %d" % exit_code - if cwd: - message += " cwd: %s" % cwd - - Exception.__init__(self, message) - self.script_args = script_args # 'args' is already used by Exception - self.exit_code = exit_code - self.output = output - self.cwd = cwd - - def message_with_output(self, output_limit=500): - if self.output: - if output_limit and len(self.output) > output_limit: - return u"%s\nLast %s characters of output:\n%s" % \ - (self, output_limit, self.output[-output_limit:]) - return u"%s\n%s" % (self, self.output) - return unicode(self) - - def command_name(self): - command_path = self.script_args - if type(command_path) is list: - command_path = command_path[0] - return os.path.basename(command_path) - - -def run_command(*args, **kwargs): - # FIXME: This should not be a global static. - # New code should use Executive.run_command directly instead - return Executive().run_command(*args, **kwargs) - - -class Executive(object): - - def _should_close_fds(self): - # We need to pass close_fds=True to work around Python bug #2320 - # (otherwise we can hang when we kill DumpRenderTree when we are running - # multiple threads). See http://bugs.python.org/issue2320 . - # Note that close_fds isn't supported on Windows, but this bug only - # shows up on Mac and Linux. - return sys.platform not in ('win32', 'cygwin') - - def _run_command_with_teed_output(self, args, teed_output): - args = map(unicode, args) # Popen will throw an exception if args are non-strings (like int()) - args = map(self._encode_argument_if_needed, args) - - child_process = subprocess.Popen(args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - close_fds=self._should_close_fds()) - - # Use our own custom wait loop because Popen ignores a tee'd - # stderr/stdout. - # FIXME: This could be improved not to flatten output to stdout. - while True: - output_line = child_process.stdout.readline() - if output_line == "" and child_process.poll() != None: - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - return child_process.poll() - # We assume that the child process wrote to us in utf-8, - # so no re-encoding is necessary before writing here. - teed_output.write(output_line) - - # FIXME: Remove this deprecated method and move callers to run_command. - # FIXME: This method is a hack to allow running command which both - # capture their output and print out to stdin. Useful for things - # like "build-webkit" where we want to display to the user that we're building - # but still have the output to stuff into a log file. - def run_and_throw_if_fail(self, args, quiet=False, decode_output=True): - # Cache the child's output locally so it can be used for error reports. - child_out_file = StringIO.StringIO() - tee_stdout = sys.stdout - if quiet: - dev_null = open(os.devnull, "w") # FIXME: Does this need an encoding? - tee_stdout = dev_null - child_stdout = tee(child_out_file, tee_stdout) - exit_code = self._run_command_with_teed_output(args, child_stdout) - if quiet: - dev_null.close() - - child_output = child_out_file.getvalue() - child_out_file.close() - - if decode_output: - child_output = child_output.decode(self._child_process_encoding()) - - if exit_code: - raise ScriptError(script_args=args, - exit_code=exit_code, - output=child_output) - return child_output - - def cpu_count(self): - if multiprocessing: - return multiprocessing.cpu_count() - # Darn. We don't have the multiprocessing package. - system_name = platform.system() - if system_name == "Darwin": - return int(self.run_command(["sysctl", "-n", "hw.ncpu"])) - elif system_name == "Windows": - return int(os.environ.get('NUMBER_OF_PROCESSORS', 1)) - elif system_name == "Linux": - num_cores = os.sysconf("SC_NPROCESSORS_ONLN") - if isinstance(num_cores, int) and num_cores > 0: - return num_cores - # This quantity is a lie but probably a reasonable guess for modern - # machines. - return 2 - - def kill_process(self, pid): - """Attempts to kill the given pid. - Will fail silently if pid does not exist or insufficient permisssions.""" - if sys.platform == "win32": - # We only use taskkill.exe on windows (not cygwin) because subprocess.pid - # is a CYGWIN pid and taskkill.exe expects a windows pid. - # Thankfully os.kill on CYGWIN handles either pid type. - command = ["taskkill.exe", "/f", "/pid", pid] - # taskkill will exit 128 if the process is not found. We should log. - self.run_command(command, error_handler=self.ignore_error) - return - - # According to http://docs.python.org/library/os.html - # os.kill isn't available on Windows. python 2.5.5 os.kill appears - # to work in cygwin, however it occasionally raises EAGAIN. - retries_left = 10 if sys.platform == "cygwin" else 1 - while retries_left > 0: - try: - retries_left -= 1 - os.kill(pid, signal.SIGKILL) - except OSError, e: - if e.errno == errno.EAGAIN: - if retries_left <= 0: - _log.warn("Failed to kill pid %s. Too many EAGAIN errors." % pid) - continue - if e.errno == errno.ESRCH: # The process does not exist. - _log.warn("Called kill_process with a non-existant pid %s" % pid) - return - raise - - def _win32_check_running_pid(self, pid): - - class PROCESSENTRY32(ctypes.Structure): - _fields_ = [("dwSize", ctypes.c_ulong), - ("cntUsage", ctypes.c_ulong), - ("th32ProcessID", ctypes.c_ulong), - ("th32DefaultHeapID", ctypes.c_ulong), - ("th32ModuleID", ctypes.c_ulong), - ("cntThreads", ctypes.c_ulong), - ("th32ParentProcessID", ctypes.c_ulong), - ("pcPriClassBase", ctypes.c_ulong), - ("dwFlags", ctypes.c_ulong), - ("szExeFile", ctypes.c_char * 260)] - - CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot - Process32First = ctypes.windll.kernel32.Process32First - Process32Next = ctypes.windll.kernel32.Process32Next - CloseHandle = ctypes.windll.kernel32.CloseHandle - TH32CS_SNAPPROCESS = 0x00000002 # win32 magic number - hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) - pe32 = PROCESSENTRY32() - pe32.dwSize = ctypes.sizeof(PROCESSENTRY32) - result = False - if not Process32First(hProcessSnap, ctypes.byref(pe32)): - _log.debug("Failed getting first process.") - CloseHandle(hProcessSnap) - return result - while True: - if pe32.th32ProcessID == pid: - result = True - break - if not Process32Next(hProcessSnap, ctypes.byref(pe32)): - break - CloseHandle(hProcessSnap) - return result - - def check_running_pid(self, pid): - """Return True if pid is alive, otherwise return False.""" - if sys.platform in ('darwin', 'linux2', 'cygwin'): - try: - os.kill(pid, 0) - return True - except OSError: - return False - elif sys.platform == 'win32': - return self._win32_check_running_pid(pid) - - assert(False) - - def _windows_image_name(self, process_name): - name, extension = os.path.splitext(process_name) - if not extension: - # taskkill expects processes to end in .exe - # If necessary we could add a flag to disable appending .exe. - process_name = "%s.exe" % name - return process_name - - def kill_all(self, process_name): - """Attempts to kill processes matching process_name. - Will fail silently if no process are found.""" - if sys.platform in ("win32", "cygwin"): - image_name = self._windows_image_name(process_name) - command = ["taskkill.exe", "/f", "/im", image_name] - # taskkill will exit 128 if the process is not found. We should log. - self.run_command(command, error_handler=self.ignore_error) - return - - # FIXME: This is inconsistent that kill_all uses TERM and kill_process - # uses KILL. Windows is always using /f (which seems like -KILL). - # We should pick one mode, or add support for switching between them. - # Note: Mac OS X 10.6 requires -SIGNALNAME before -u USER - command = ["killall", "-TERM", "-u", os.getenv("USER"), process_name] - # killall returns 1 if no process can be found and 2 on command error. - # FIXME: We should pass a custom error_handler to allow only exit_code 1. - # We should log in exit_code == 1 - self.run_command(command, error_handler=self.ignore_error) - - # Error handlers do not need to be static methods once all callers are - # updated to use an Executive object. - - @staticmethod - def default_error_handler(error): - raise error - - @staticmethod - def ignore_error(error): - pass - - def _compute_stdin(self, input): - """Returns (stdin, string_to_communicate)""" - # FIXME: We should be returning /dev/null for stdin - # or closing stdin after process creation to prevent - # child processes from getting input from the user. - if not input: - return (None, None) - if hasattr(input, "read"): # Check if the input is a file. - return (input, None) # Assume the file is in the right encoding. - - # Popen in Python 2.5 and before does not automatically encode unicode objects. - # http://bugs.python.org/issue5290 - # See https://bugs.webkit.org/show_bug.cgi?id=37528 - # for an example of a regresion caused by passing a unicode string directly. - # FIXME: We may need to encode differently on different platforms. - if isinstance(input, unicode): - input = input.encode(self._child_process_encoding()) - return (subprocess.PIPE, input) - - def _command_for_printing(self, args): - """Returns a print-ready string representing command args. - The string should be copy/paste ready for execution in a shell.""" - escaped_args = [] - for arg in args: - if isinstance(arg, unicode): - # Escape any non-ascii characters for easy copy/paste - arg = arg.encode("unicode_escape") - # FIXME: Do we need to fix quotes here? - escaped_args.append(arg) - return " ".join(escaped_args) - - # FIXME: run_and_throw_if_fail should be merged into this method. - def run_command(self, - args, - cwd=None, - input=None, - error_handler=None, - return_exit_code=False, - return_stderr=True, - decode_output=True): - """Popen wrapper for convenience and to work around python bugs.""" - assert(isinstance(args, list) or isinstance(args, tuple)) - start_time = time.time() - args = map(unicode, args) # Popen will throw an exception if args are non-strings (like int()) - args = map(self._encode_argument_if_needed, args) - - stdin, string_to_communicate = self._compute_stdin(input) - stderr = subprocess.STDOUT if return_stderr else None - - process = subprocess.Popen(args, - stdin=stdin, - stdout=subprocess.PIPE, - stderr=stderr, - cwd=cwd, - close_fds=self._should_close_fds()) - output = process.communicate(string_to_communicate)[0] - - # run_command automatically decodes to unicode() unless explicitly told not to. - if decode_output: - output = output.decode(self._child_process_encoding()) - - # wait() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - exit_code = process.wait() - - _log.debug('"%s" took %.2fs' % (self._command_for_printing(args), time.time() - start_time)) - - if return_exit_code: - return exit_code - - if exit_code: - script_error = ScriptError(script_args=args, - exit_code=exit_code, - output=output, - cwd=cwd) - (error_handler or self.default_error_handler)(script_error) - return output - - def _child_process_encoding(self): - # Win32 Python 2.x uses CreateProcessA rather than CreateProcessW - # to launch subprocesses, so we have to encode arguments using the - # current code page. - if sys.platform == 'win32' and versioning.compare_version(sys, '3.0')[0] < 0: - return 'mbcs' - # All other platforms use UTF-8. - # FIXME: Using UTF-8 on Cygwin will confuse Windows-native commands - # which will expect arguments to be encoded using the current code - # page. - return 'utf-8' - - def _should_encode_child_process_arguments(self): - # Cygwin's Python's os.execv doesn't support unicode command - # arguments, and neither does Cygwin's execv itself. - if sys.platform == 'cygwin': - return True - - # Win32 Python 2.x uses CreateProcessA rather than CreateProcessW - # to launch subprocesses, so we have to encode arguments using the - # current code page. - if sys.platform == 'win32' and versioning.compare_version(sys, '3.0')[0] < 0: - return True - - return False - - def _encode_argument_if_needed(self, argument): - if not self._should_encode_child_process_arguments(): - return argument - return argument.encode(self._child_process_encoding()) diff --git a/WebKitTools/Scripts/webkitpy/common/system/executive_mock.py b/WebKitTools/Scripts/webkitpy/common/system/executive_mock.py deleted file mode 100644 index c1cf999..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/executive_mock.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# FIXME: Implement the rest of the interface as needed for testing :). - -# FIXME: Unify with tool/mocktool.MockExecutive. - - -class MockExecutive2(object): - def __init__(self, output='', exit_code=0, exception=None, - run_command_fn=None): - self._output = output - self._exit_code = exit_code - self._exception = exception - self._run_command_fn = run_command_fn - - def cpu_count(self): - return 2 - - def kill_all(self, process_name): - pass - - def kill_process(self, pid): - pass - - def run_command(self, arg_list, return_exit_code=False, - decode_output=False): - if self._exception: - raise self._exception - if return_exit_code: - return self._exit_code - if self._run_command_fn: - return self._run_command_fn(arg_list) - return self._output diff --git a/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py deleted file mode 100644 index b8fd82e..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/executive_unittest.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# Copyright (C) 2009 Daniel Bates (dbates@intudata.com). All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import os -import signal -import subprocess -import sys -import unittest - -from webkitpy.common.system.executive import Executive, run_command, ScriptError -from webkitpy.test import cat, echo - - -def never_ending_command(): - """Arguments for a command that will never end (useful for testing process - killing). It should be a process that is unlikely to already be running - because all instances will be killed.""" - if sys.platform == 'win32': - return ['wmic'] - return ['yes'] - - -class ExecutiveTest(unittest.TestCase): - - def test_run_command_with_bad_command(self): - def run_bad_command(): - run_command(["foo_bar_command_blah"], error_handler=Executive.ignore_error, return_exit_code=True) - self.failUnlessRaises(OSError, run_bad_command) - - def test_run_command_args_type(self): - executive = Executive() - self.assertRaises(AssertionError, executive.run_command, "echo") - self.assertRaises(AssertionError, executive.run_command, u"echo") - executive.run_command(echo.command_arguments('foo')) - executive.run_command(tuple(echo.command_arguments('foo'))) - - def test_run_command_with_unicode(self): - """Validate that it is safe to pass unicode() objects - to Executive.run* methods, and they will return unicode() - objects by default unless decode_output=False""" - unicode_tor_input = u"WebKit \u2661 Tor Arne Vestb\u00F8!" - if sys.platform == 'win32': - encoding = 'mbcs' - else: - encoding = 'utf-8' - encoded_tor = unicode_tor_input.encode(encoding) - # On Windows, we expect the unicode->mbcs->unicode roundtrip to be - # lossy. On other platforms, we expect a lossless roundtrip. - if sys.platform == 'win32': - unicode_tor_output = encoded_tor.decode(encoding) - else: - unicode_tor_output = unicode_tor_input - - executive = Executive() - - output = executive.run_command(cat.command_arguments(), input=unicode_tor_input) - self.assertEquals(output, unicode_tor_output) - - output = executive.run_command(echo.command_arguments("-n", unicode_tor_input)) - self.assertEquals(output, unicode_tor_output) - - output = executive.run_command(echo.command_arguments("-n", unicode_tor_input), decode_output=False) - self.assertEquals(output, encoded_tor) - - # Make sure that str() input also works. - output = executive.run_command(cat.command_arguments(), input=encoded_tor, decode_output=False) - self.assertEquals(output, encoded_tor) - - # FIXME: We should only have one run* method to test - output = executive.run_and_throw_if_fail(echo.command_arguments("-n", unicode_tor_input), quiet=True) - self.assertEquals(output, unicode_tor_output) - - output = executive.run_and_throw_if_fail(echo.command_arguments("-n", unicode_tor_input), quiet=True, decode_output=False) - self.assertEquals(output, encoded_tor) - - def test_kill_process(self): - executive = Executive() - process = subprocess.Popen(never_ending_command(), stdout=subprocess.PIPE) - self.assertEqual(process.poll(), None) # Process is running - executive.kill_process(process.pid) - # Note: Can't use a ternary since signal.SIGKILL is undefined for sys.platform == "win32" - if sys.platform == "win32": - expected_exit_code = 1 - else: - expected_exit_code = -signal.SIGKILL - self.assertEqual(process.wait(), expected_exit_code) - # Killing again should fail silently. - executive.kill_process(process.pid) - - def _assert_windows_image_name(self, name, expected_windows_name): - executive = Executive() - windows_name = executive._windows_image_name(name) - self.assertEqual(windows_name, expected_windows_name) - - def test_windows_image_name(self): - self._assert_windows_image_name("foo", "foo.exe") - self._assert_windows_image_name("foo.exe", "foo.exe") - self._assert_windows_image_name("foo.com", "foo.com") - # If the name looks like an extension, even if it isn't - # supposed to, we have no choice but to return the original name. - self._assert_windows_image_name("foo.baz", "foo.baz") - self._assert_windows_image_name("foo.baz.exe", "foo.baz.exe") - - def test_kill_all(self): - executive = Executive() - # We use "yes" because it loops forever. - process = subprocess.Popen(never_ending_command(), stdout=subprocess.PIPE) - self.assertEqual(process.poll(), None) # Process is running - executive.kill_all(never_ending_command()[0]) - # Note: Can't use a ternary since signal.SIGTERM is undefined for sys.platform == "win32" - if sys.platform == "cygwin": - expected_exit_code = 0 # os.kill results in exit(0) for this process. - elif sys.platform == "win32": - expected_exit_code = 1 - else: - expected_exit_code = -signal.SIGTERM - self.assertEqual(process.wait(), expected_exit_code) - # Killing again should fail silently. - executive.kill_all(never_ending_command()[0]) - - def test_check_running_pid(self): - executive = Executive() - self.assertTrue(executive.check_running_pid(os.getpid())) - # Maximum pid number on Linux is 32768 by default - self.assertFalse(executive.check_running_pid(100000)) diff --git a/WebKitTools/Scripts/webkitpy/common/system/file_lock.py b/WebKitTools/Scripts/webkitpy/common/system/file_lock.py deleted file mode 100644 index 7296958..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/file_lock.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UNIVERSITY OF SZEGED OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""This class helps to lock files exclusively across processes.""" - -import logging -import os -import sys -import time - - -_log = logging.getLogger("webkitpy.common.system.file_lock") - - -class FileLock(object): - - def __init__(self, lock_file_path, max_wait_time_sec=20): - self._lock_file_path = lock_file_path - self._lock_file_descriptor = None - self._max_wait_time_sec = max_wait_time_sec - - def _create_lock(self): - if sys.platform in ('darwin', 'linux2', 'cygwin'): - import fcntl - fcntl.flock(self._lock_file_descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB) - elif sys.platform == 'win32': - import msvcrt - msvcrt.locking(self._lock_file_descriptor, msvcrt.LK_NBLCK, 32) - - def _remove_lock(self): - if sys.platform in ('darwin', 'linux2', 'cygwin'): - import fcntl - fcntl.flock(self._lock_file_descriptor, fcntl.LOCK_UN) - elif sys.platform == 'win32': - import msvcrt - msvcrt.locking(self._lock_file_descriptor, msvcrt.LK_UNLCK, 32) - - def acquire_lock(self): - self._lock_file_descriptor = os.open(self._lock_file_path, os.O_TRUNC | os.O_CREAT) - start_time = time.time() - while True: - try: - self._create_lock() - return True - except IOError: - if time.time() - start_time > self._max_wait_time_sec: - _log.debug("File locking failed: %s" % str(sys.exc_info())) - os.close(self._lock_file_descriptor) - self._lock_file_descriptor = None - return False - - def release_lock(self): - try: - if self._lock_file_descriptor: - self._remove_lock() - os.close(self._lock_file_descriptor) - self._lock_file_descriptor = None - os.unlink(self._lock_file_path) - except (IOError, OSError): - _log.debug("Warning in release lock: %s" % str(sys.exc_info())) diff --git a/WebKitTools/Scripts/webkitpy/common/system/file_lock_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/file_lock_unittest.py deleted file mode 100644 index c5c1db3..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/file_lock_unittest.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UNIVERSITY OF SZEGED OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import os -import tempfile -import unittest - -from webkitpy.common.system.file_lock import FileLock - - -class FileLockTest(unittest.TestCase): - - def setUp(self): - self._lock_name = "TestWebKit" + str(os.getpid()) + ".lock" - self._lock_path = os.path.join(tempfile.gettempdir(), self._lock_name) - self._file_lock1 = FileLock(self._lock_path, 1) - self._file_lock2 = FileLock(self._lock_path, 1) - - def tearDown(self): - self._file_lock1.release_lock() - self._file_lock2.release_lock() - - def test_lock_lifecycle(self): - # Create the lock. - self._file_lock1.acquire_lock() - self.assertTrue(os.path.exists(self._lock_path)) - - # Try to lock again. - self.assertFalse(self._file_lock2.acquire_lock()) - - # Release the lock. - self._file_lock1.release_lock() - self.assertFalse(os.path.exists(self._lock_path)) - - def test_stuck_lock(self): - open(self._lock_path, 'w').close() - self._file_lock1.acquire_lock() - self._file_lock1.release_lock() diff --git a/WebKitTools/Scripts/webkitpy/common/system/filesystem.py b/WebKitTools/Scripts/webkitpy/common/system/filesystem.py deleted file mode 100644 index c7efde3..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/filesystem.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Wrapper object for the file system / source tree.""" - -from __future__ import with_statement - -import codecs -import errno -import os -import tempfile - - -class FileSystem(object): - """FileSystem interface for webkitpy. - - Unless otherwise noted, all paths are allowed to be either absolute - or relative.""" - - def exists(self, path): - """Return whether the path exists in the filesystem.""" - return os.path.exists(path) - - def isdir(self, path): - """Return whether the path refers to a directory.""" - return os.path.isdir(path) - - def join(self, *comps): - """Return the path formed by joining the components.""" - return os.path.join(*comps) - - def listdir(self, path): - """Return the contents of the directory pointed to by path.""" - return os.listdir(path) - - def mkdtemp(self, **kwargs): - """Create and return a uniquely named directory. - - This is like tempfile.mkdtemp, but if used in a with statement - the directory will self-delete at the end of the block (if the - directory is empty; non-empty directories raise errors). The - directory can be safely deleted inside the block as well, if so - desired.""" - class TemporaryDirectory(object): - def __init__(self, **kwargs): - self._kwargs = kwargs - self._directory_path = None - - def __enter__(self): - self._directory_path = tempfile.mkdtemp(**self._kwargs) - return self._directory_path - - def __exit__(self, type, value, traceback): - # Only self-delete if necessary. - - # FIXME: Should we delete non-empty directories? - if os.path.exists(self._directory_path): - os.rmdir(self._directory_path) - - return TemporaryDirectory(**kwargs) - - def maybe_make_directory(self, *path): - """Create the specified directory if it doesn't already exist.""" - try: - os.makedirs(os.path.join(*path)) - except OSError, e: - if e.errno != errno.EEXIST: - raise - - def read_binary_file(self, path): - """Return the contents of the file at the given path as a byte string.""" - with file(path, 'rb') as f: - return f.read() - - def read_text_file(self, path): - """Return the contents of the file at the given path as a Unicode string. - - The file is read assuming it is a UTF-8 encoded file with no BOM.""" - with codecs.open(path, 'r', 'utf8') as f: - return f.read() - - def write_binary_file(self, path, contents): - """Write the contents to the file at the given location.""" - with file(path, 'wb') as f: - f.write(contents) - - def write_text_file(self, path, contents): - """Write the contents to the file at the given location. - - The file is written encoded as UTF-8 with no BOM.""" - with codecs.open(path, 'w', 'utf8') as f: - f.write(contents) diff --git a/WebKitTools/Scripts/webkitpy/common/system/filesystem_mock.py b/WebKitTools/Scripts/webkitpy/common/system/filesystem_mock.py deleted file mode 100644 index 4e6d3da..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/filesystem_mock.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import errno -import os -import path - - -class MockFileSystem(object): - def __init__(self, files={}): - """Initializes a "mock" filesystem that can be used to completely - stub out a filesystem. - - Args: - files: a dict of filenames -> file contents. A file contents - value of None is used to indicate that the file should - not exist. - """ - self.files = files - - def exists(self, path): - if path in self.files: - return self.files[path] is not None - return False - - def join(self, *comps): - # os.path.join ignores trailing slashes on components (i.e. - # join('foo/', 'bar') and join('foo', 'bar') produce the same result), - # we emulate that behavior. - trimmed_comps = [] - for comp in comps: - if len(comp) and comp[-1] == '/': - trimmed_comps.append(comp[0:-1]) - else: - trimmed_comps.append(comp) - return '/'.join(trimmed_comps) - - def maybe_make_directory(self, *path): - # FIXME: Implement such that subsequent calls to isdir() work? - pass - - def read_text_file(self, path): - return self.read_binary_file(path) - - def read_binary_file(self, path): - if path in self.files: - if self.files[path] is None: - raise IOError(errno.ENOENT, path, os.strerror(errno.ENOENT)) - return self.files[path] - - def write_text_file(self, path, contents): - return self.write_binary_file(path, contents) - - def write_binary_file(self, path, contents): - self.files[path] = contents diff --git a/WebKitTools/Scripts/webkitpy/common/system/filesystem_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/filesystem_unittest.py deleted file mode 100644 index 95684b7..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/filesystem_unittest.py +++ /dev/null @@ -1,157 +0,0 @@ -# vim: set fileencoding=utf-8 : -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# NOTE: The fileencoding comment on the first line of the file is -# important; without it, Python will choke while trying to parse the file, -# since it includes non-ASCII characters. - -from __future__ import with_statement - -import os -import stat -import sys -import tempfile -import unittest - -from filesystem import FileSystem - - -class FileSystemTest(unittest.TestCase): - def setUp(self): - self._this_dir = os.path.dirname(os.path.abspath(__file__)) - self._missing_file = os.path.join(self._this_dir, 'missing_file.py') - self._this_file = os.path.join(self._this_dir, 'filesystem_unittest.py') - - def test_exists__true(self): - fs = FileSystem() - self.assertTrue(fs.exists(self._this_file)) - - def test_exists__false(self): - fs = FileSystem() - self.assertFalse(fs.exists(self._missing_file)) - - def test_isdir__true(self): - fs = FileSystem() - self.assertTrue(fs.isdir(self._this_dir)) - - def test_isdir__false(self): - fs = FileSystem() - self.assertFalse(fs.isdir(self._this_file)) - - def test_join(self): - fs = FileSystem() - self.assertEqual(fs.join('foo', 'bar'), - os.path.join('foo', 'bar')) - - def test_listdir(self): - fs = FileSystem() - with fs.mkdtemp(prefix='filesystem_unittest_') as d: - self.assertEqual(fs.listdir(d), []) - new_file = os.path.join(d, 'foo') - fs.write_text_file(new_file, u'foo') - self.assertEqual(fs.listdir(d), ['foo']) - os.remove(new_file) - - def test_maybe_make_directory__success(self): - fs = FileSystem() - - with fs.mkdtemp(prefix='filesystem_unittest_') as base_path: - sub_path = os.path.join(base_path, "newdir") - self.assertFalse(os.path.exists(sub_path)) - self.assertFalse(fs.isdir(sub_path)) - - fs.maybe_make_directory(sub_path) - self.assertTrue(os.path.exists(sub_path)) - self.assertTrue(fs.isdir(sub_path)) - - # Make sure we can re-create it. - fs.maybe_make_directory(sub_path) - self.assertTrue(os.path.exists(sub_path)) - self.assertTrue(fs.isdir(sub_path)) - - # Clean up. - os.rmdir(sub_path) - - self.assertFalse(os.path.exists(base_path)) - self.assertFalse(fs.isdir(base_path)) - - def test_maybe_make_directory__failure(self): - # FIXME: os.chmod() doesn't work on Windows to set directories - # as readonly, so we skip this test for now. - if sys.platform in ('win32', 'cygwin'): - return - - fs = FileSystem() - with fs.mkdtemp(prefix='filesystem_unittest_') as d: - # Remove write permissions on the parent directory. - os.chmod(d, stat.S_IRUSR) - - # Now try to create a sub directory - should fail. - sub_dir = fs.join(d, 'subdir') - self.assertRaises(OSError, fs.maybe_make_directory, sub_dir) - - # Clean up in case the test failed and we did create the - # directory. - if os.path.exists(sub_dir): - os.rmdir(sub_dir) - - def test_read_and_write_file(self): - fs = FileSystem() - text_path = None - binary_path = None - - unicode_text_string = u'Ūnĭcōde̽' - hex_equivalent = '\xC5\xAA\x6E\xC4\xAD\x63\xC5\x8D\x64\x65\xCC\xBD' - try: - text_path = tempfile.mktemp(prefix='tree_unittest_') - binary_path = tempfile.mktemp(prefix='tree_unittest_') - fs.write_text_file(text_path, unicode_text_string) - contents = fs.read_binary_file(text_path) - self.assertEqual(contents, hex_equivalent) - - fs.write_text_file(binary_path, hex_equivalent) - text_contents = fs.read_text_file(binary_path) - self.assertEqual(text_contents, unicode_text_string) - except: - if text_path: - os.remove(text_path) - if binary_path: - os.remove(binary_path) - - def test_read_binary_file__missing(self): - fs = FileSystem() - self.assertRaises(IOError, fs.read_binary_file, self._missing_file) - - def test_read_text_file__missing(self): - fs = FileSystem() - self.assertRaises(IOError, fs.read_text_file, self._missing_file) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/system/fileutils.py b/WebKitTools/Scripts/webkitpy/common/system/fileutils.py deleted file mode 100644 index 55821f8..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/fileutils.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2010 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import sys - - -def make_stdout_binary(): - """Puts sys.stdout into binary mode (on platforms that have a distinction - between text and binary mode).""" - if sys.platform != 'win32' or not hasattr(sys.stdout, 'fileno'): - return - import msvcrt - import os - msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) diff --git a/WebKitTools/Scripts/webkitpy/common/system/logtesting.py b/WebKitTools/Scripts/webkitpy/common/system/logtesting.py deleted file mode 100644 index e361cb5..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/logtesting.py +++ /dev/null @@ -1,258 +0,0 @@ -# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Supports the unit-testing of logging code. - -Provides support for unit-testing messages logged using the built-in -logging module. - -Inherit from the LoggingTestCase class for basic testing needs. For -more advanced needs (e.g. unit-testing methods that configure logging), -see the TestLogStream class, and perhaps also the LogTesting class. - -""" - -import logging -import unittest - - -class TestLogStream(object): - - """Represents a file-like object for unit-testing logging. - - This is meant for passing to the logging.StreamHandler constructor. - Log messages captured by instances of this object can be tested - using self.assertMessages() below. - - """ - - def __init__(self, test_case): - """Create an instance. - - Args: - test_case: A unittest.TestCase instance. - - """ - self._test_case = test_case - self.messages = [] - """A list of log messages written to the stream.""" - - # Python documentation says that any object passed to the StreamHandler - # constructor should support write() and flush(): - # - # http://docs.python.org/library/logging.html#module-logging.handlers - def write(self, message): - self.messages.append(message) - - def flush(self): - pass - - def assertMessages(self, messages): - """Assert that the given messages match the logged messages. - - messages: A list of log message strings. - - """ - self._test_case.assertEquals(messages, self.messages) - - -class LogTesting(object): - - """Supports end-to-end unit-testing of log messages. - - Sample usage: - - class SampleTest(unittest.TestCase): - - def setUp(self): - self._log = LogTesting.setUp(self) # Turn logging on. - - def tearDown(self): - self._log.tearDown() # Turn off and reset logging. - - def test_logging_in_some_method(self): - call_some_method() # Contains calls to _log.info(), etc. - - # Check the resulting log messages. - self._log.assertMessages(["INFO: expected message #1", - "WARNING: expected message #2"]) - - """ - - def __init__(self, test_stream, handler): - """Create an instance. - - This method should never be called directly. Instances should - instead be created using the static setUp() method. - - Args: - test_stream: A TestLogStream instance. - handler: The handler added to the logger. - - """ - self._test_stream = test_stream - self._handler = handler - - @staticmethod - def _getLogger(): - """Return the logger being tested.""" - # It is possible we might want to return something other than - # the root logger in some special situation. For now, the - # root logger seems to suffice. - return logging.getLogger() - - @staticmethod - def setUp(test_case, logging_level=logging.INFO): - """Configure logging for unit testing. - - Configures the root logger to log to a testing log stream. - Only messages logged at or above the given level are logged - to the stream. Messages logged to the stream are formatted - in the following way, for example-- - - "INFO: This is a test log message." - - This method should normally be called in the setUp() method - of a unittest.TestCase. See the docstring of this class - for more details. - - Returns: - A LogTesting instance. - - Args: - test_case: A unittest.TestCase instance. - logging_level: An integer logging level that is the minimum level - of log messages you would like to test. - - """ - stream = TestLogStream(test_case) - handler = logging.StreamHandler(stream) - handler.setLevel(logging_level) - formatter = logging.Formatter("%(levelname)s: %(message)s") - handler.setFormatter(formatter) - - # Notice that we only change the root logger by adding a handler - # to it. In particular, we do not reset its level using - # logger.setLevel(). This ensures that we have not interfered - # with how the code being tested may have configured the root - # logger. - logger = LogTesting._getLogger() - logger.addHandler(handler) - - return LogTesting(stream, handler) - - def tearDown(self): - """Assert there are no remaining log messages, and reset logging. - - This method asserts that there are no more messages in the array of - log messages, and then restores logging to its original state. - This method should normally be called in the tearDown() method of a - unittest.TestCase. See the docstring of this class for more details. - - """ - self.assertMessages([]) - logger = LogTesting._getLogger() - logger.removeHandler(self._handler) - - def messages(self): - """Return the current list of log messages.""" - return self._test_stream.messages - - # FIXME: Add a clearMessages() method for cases where the caller - # deliberately doesn't want to assert every message. - - # We clear the log messages after asserting since they are no longer - # needed after asserting. This serves two purposes: (1) it simplifies - # the calling code when we want to check multiple logging calls in a - # single test method, and (2) it lets us check in the tearDown() method - # that there are no remaining log messages to be asserted. - # - # The latter ensures that no extra log messages are getting logged that - # the caller might not be aware of or may have forgotten to check for. - # This gets us a bit more mileage out of our tests without writing any - # additional code. - def assertMessages(self, messages): - """Assert the current array of log messages, and clear its contents. - - Args: - messages: A list of log message strings. - - """ - try: - self._test_stream.assertMessages(messages) - finally: - # We want to clear the array of messages even in the case of - # an Exception (e.g. an AssertionError). Otherwise, another - # AssertionError can occur in the tearDown() because the - # array might not have gotten emptied. - self._test_stream.messages = [] - - -# This class needs to inherit from unittest.TestCase. Otherwise, the -# setUp() and tearDown() methods will not get fired for test case classes -# that inherit from this class -- even if the class inherits from *both* -# unittest.TestCase and LoggingTestCase. -# -# FIXME: Rename this class to LoggingTestCaseBase to be sure that -# the unittest module does not interpret this class as a unittest -# test case itself. -class LoggingTestCase(unittest.TestCase): - - """Supports end-to-end unit-testing of log messages. - - Sample usage: - - class SampleTest(LoggingTestCase): - - def test_logging_in_some_method(self): - call_some_method() # Contains calls to _log.info(), etc. - - # Check the resulting log messages. - self.assertLog(["INFO: expected message #1", - "WARNING: expected message #2"]) - - """ - - def setUp(self): - self._log = LogTesting.setUp(self) - - def tearDown(self): - self._log.tearDown() - - def logMessages(self): - """Return the current list of log messages.""" - return self._log.messages() - - # FIXME: Add a clearMessages() method for cases where the caller - # deliberately doesn't want to assert every message. - - # See the code comments preceding LogTesting.assertMessages() for - # an explanation of why we clear the array of messages after - # asserting its contents. - def assertLog(self, messages): - """Assert the current array of log messages, and clear its contents. - - Args: - messages: A list of log message strings. - - """ - self._log.assertMessages(messages) diff --git a/WebKitTools/Scripts/webkitpy/common/system/logutils.py b/WebKitTools/Scripts/webkitpy/common/system/logutils.py deleted file mode 100644 index cd4e60f..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/logutils.py +++ /dev/null @@ -1,207 +0,0 @@ -# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Supports webkitpy logging.""" - -# FIXME: Move this file to webkitpy/python24 since logging needs to -# be configured prior to running version-checking code. - -import logging -import os -import sys - -import webkitpy - - -_log = logging.getLogger(__name__) - -# We set these directory paths lazily in get_logger() below. -_scripts_dir = "" -"""The normalized, absolute path to the ...Scripts directory.""" - -_webkitpy_dir = "" -"""The normalized, absolute path to the ...Scripts/webkitpy directory.""" - - -def _normalize_path(path): - """Return the given path normalized. - - Converts a path to an absolute path, removes any trailing slashes, - removes any extension, and lower-cases it. - - """ - path = os.path.abspath(path) - path = os.path.normpath(path) - path = os.path.splitext(path)[0] # Remove the extension, if any. - path = path.lower() - - return path - - -# Observe that the implementation of this function does not require -# the use of any hard-coded strings like "webkitpy", etc. -# -# The main benefit this function has over using-- -# -# _log = logging.getLogger(__name__) -# -# is that get_logger() returns the same value even if __name__ is -# "__main__" -- i.e. even if the module is the script being executed -# from the command-line. -def get_logger(path): - """Return a logging.logger for the given path. - - Returns: - A logger whose name is the name of the module corresponding to - the given path. If the module is in webkitpy, the name is - the fully-qualified dotted module name beginning with webkitpy.... - Otherwise, the name is the base name of the module (i.e. without - any dotted module name prefix). - - Args: - path: The path of the module. Normally, this parameter should be - the __file__ variable of the module. - - Sample usage: - - import webkitpy.common.system.logutils as logutils - - _log = logutils.get_logger(__file__) - - """ - # Since we assign to _scripts_dir and _webkitpy_dir in this function, - # we need to declare them global. - global _scripts_dir - global _webkitpy_dir - - path = _normalize_path(path) - - # Lazily evaluate _webkitpy_dir and _scripts_dir. - if not _scripts_dir: - # The normalized, absolute path to ...Scripts/webkitpy/__init__. - webkitpy_path = _normalize_path(webkitpy.__file__) - - _webkitpy_dir = os.path.split(webkitpy_path)[0] - _scripts_dir = os.path.split(_webkitpy_dir)[0] - - if path.startswith(_webkitpy_dir): - # Remove the initial Scripts directory portion, so the path - # starts with /webkitpy, for example "/webkitpy/init/logutils". - path = path[len(_scripts_dir):] - - parts = [] - while True: - (path, tail) = os.path.split(path) - if not tail: - break - parts.insert(0, tail) - - logger_name = ".".join(parts) # For example, webkitpy.common.system.logutils. - else: - # The path is outside of webkitpy. Default to the basename - # without the extension. - basename = os.path.basename(path) - logger_name = os.path.splitext(basename)[0] - - return logging.getLogger(logger_name) - - -def _default_handlers(stream): - """Return a list of the default logging handlers to use. - - Args: - stream: See the configure_logging() docstring. - - """ - # Create the filter. - def should_log(record): - """Return whether a logging.LogRecord should be logged.""" - # FIXME: Enable the logging of autoinstall messages once - # autoinstall is adjusted. Currently, autoinstall logs - # INFO messages when importing already-downloaded packages, - # which is too verbose. - if record.name.startswith("webkitpy.thirdparty.autoinstall"): - return False - return True - - logging_filter = logging.Filter() - logging_filter.filter = should_log - - # Create the handler. - handler = logging.StreamHandler(stream) - formatter = logging.Formatter("%(name)s: [%(levelname)s] %(message)s") - handler.setFormatter(formatter) - handler.addFilter(logging_filter) - - return [handler] - - -def configure_logging(logging_level=None, logger=None, stream=None, - handlers=None): - """Configure logging for standard purposes. - - Returns: - A list of references to the logging handlers added to the root - logger. This allows the caller to later remove the handlers - using logger.removeHandler. This is useful primarily during unit - testing where the caller may want to configure logging temporarily - and then undo the configuring. - - Args: - logging_level: The minimum logging level to log. Defaults to - logging.INFO. - logger: A logging.logger instance to configure. This parameter - should be used only in unit tests. Defaults to the - root logger. - stream: A file-like object to which to log used in creating the default - handlers. The stream must define an "encoding" data attribute, - or else logging raises an error. Defaults to sys.stderr. - handlers: A list of logging.Handler instances to add to the logger - being configured. If this parameter is provided, then the - stream parameter is not used. - - """ - # If the stream does not define an "encoding" data attribute, the - # logging module can throw an error like the following: - # - # Traceback (most recent call last): - # File "/System/Library/Frameworks/Python.framework/Versions/2.6/... - # lib/python2.6/logging/__init__.py", line 761, in emit - # self.stream.write(fs % msg.encode(self.stream.encoding)) - # LookupError: unknown encoding: unknown - if logging_level is None: - logging_level = logging.INFO - if logger is None: - logger = logging.getLogger() - if stream is None: - stream = sys.stderr - if handlers is None: - handlers = _default_handlers(stream) - - logger.setLevel(logging_level) - - for handler in handlers: - logger.addHandler(handler) - - _log.debug("Debug logging enabled.") - - return handlers diff --git a/WebKitTools/Scripts/webkitpy/common/system/logutils_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/logutils_unittest.py deleted file mode 100644 index a4a6496..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/logutils_unittest.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Unit tests for logutils.py.""" - -import logging -import os -import unittest - -from webkitpy.common.system.logtesting import LogTesting -from webkitpy.common.system.logtesting import TestLogStream -import webkitpy.common.system.logutils as logutils - - -class GetLoggerTest(unittest.TestCase): - - """Tests get_logger().""" - - def test_get_logger_in_webkitpy(self): - logger = logutils.get_logger(__file__) - self.assertEquals(logger.name, "webkitpy.common.system.logutils_unittest") - - def test_get_logger_not_in_webkitpy(self): - # Temporarily change the working directory so that we - # can test get_logger() for a path outside of webkitpy. - working_directory = os.getcwd() - root_dir = "/" - os.chdir(root_dir) - - logger = logutils.get_logger("/WebKitTools/Scripts/test-webkitpy") - self.assertEquals(logger.name, "test-webkitpy") - - logger = logutils.get_logger("/WebKitTools/Scripts/test-webkitpy.py") - self.assertEquals(logger.name, "test-webkitpy") - - os.chdir(working_directory) - - -class ConfigureLoggingTestBase(unittest.TestCase): - - """Base class for configure_logging() unit tests.""" - - def _logging_level(self): - raise Exception("Not implemented.") - - def setUp(self): - log_stream = TestLogStream(self) - - # Use a logger other than the root logger or one prefixed with - # "webkitpy." so as not to conflict with test-webkitpy logging. - logger = logging.getLogger("unittest") - - # Configure the test logger not to pass messages along to the - # root logger. This prevents test messages from being - # propagated to loggers used by test-webkitpy logging (e.g. - # the root logger). - logger.propagate = False - - logging_level = self._logging_level() - self._handlers = logutils.configure_logging(logging_level=logging_level, - logger=logger, - stream=log_stream) - self._log = logger - self._log_stream = log_stream - - def tearDown(self): - """Reset logging to its original state. - - This method ensures that the logging configuration set up - for a unit test does not affect logging in other unit tests. - - """ - logger = self._log - for handler in self._handlers: - logger.removeHandler(handler) - - def _assert_log_messages(self, messages): - """Assert that the logged messages equal the given messages.""" - self._log_stream.assertMessages(messages) - - -class ConfigureLoggingTest(ConfigureLoggingTestBase): - - """Tests configure_logging() with the default logging level.""" - - def _logging_level(self): - return None - - def test_info_message(self): - self._log.info("test message") - self._assert_log_messages(["unittest: [INFO] test message\n"]) - - def test_below_threshold_message(self): - # We test the boundary case of a logging level equal to 19. - # In practice, we will probably only be calling log.debug(), - # which corresponds to a logging level of 10. - level = logging.INFO - 1 # Equals 19. - self._log.log(level, "test message") - self._assert_log_messages([]) - - def test_two_messages(self): - self._log.info("message1") - self._log.info("message2") - self._assert_log_messages(["unittest: [INFO] message1\n", - "unittest: [INFO] message2\n"]) - - -class ConfigureLoggingCustomLevelTest(ConfigureLoggingTestBase): - - """Tests configure_logging() with a custom logging level.""" - - _level = 36 - - def _logging_level(self): - return self._level - - def test_logged_message(self): - self._log.log(self._level, "test message") - self._assert_log_messages(["unittest: [Level 36] test message\n"]) - - def test_below_threshold_message(self): - self._log.log(self._level - 1, "test message") - self._assert_log_messages([]) diff --git a/WebKitTools/Scripts/webkitpy/common/system/ospath.py b/WebKitTools/Scripts/webkitpy/common/system/ospath.py deleted file mode 100644 index aed7a3d..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/ospath.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Contains a substitute for Python 2.6's os.path.relpath().""" - -import os - - -# This function is a replacement for os.path.relpath(), which is only -# available in Python 2.6: -# -# http://docs.python.org/library/os.path.html#os.path.relpath -# -# It should behave essentially the same as os.path.relpath(), except for -# returning None on paths not contained in abs_start_path. -def relpath(path, start_path, os_path_abspath=None): - """Return a path relative to the given start path, or None. - - Returns None if the path is not contained in the directory start_path. - - Args: - path: An absolute or relative path to convert to a relative path. - start_path: The path relative to which the given path should be - converted. - os_path_abspath: A replacement function for unit testing. This - function should strip trailing slashes just like - os.path.abspath(). Defaults to os.path.abspath. - - """ - if os_path_abspath is None: - os_path_abspath = os.path.abspath - - # Since os_path_abspath() calls os.path.normpath()-- - # - # (see http://docs.python.org/library/os.path.html#os.path.abspath ) - # - # it also removes trailing slashes and converts forward and backward - # slashes to the preferred slash os.sep. - start_path = os_path_abspath(start_path) - path = os_path_abspath(path) - - if not path.lower().startswith(start_path.lower()): - # Then path is outside the directory given by start_path. - return None - - rel_path = path[len(start_path):] - - if not rel_path: - # Then the paths are the same. - pass - elif rel_path[0] == os.sep: - # It is probably sufficient to remove just the first character - # since os.path.normpath() collapses separators, but we use - # lstrip() just to be sure. - rel_path = rel_path.lstrip(os.sep) - else: - # We are in the case typified by the following example: - # - # start_path = "/tmp/foo" - # path = "/tmp/foobar" - # rel_path = "bar" - return None - - return rel_path diff --git a/WebKitTools/Scripts/webkitpy/common/system/ospath_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/ospath_unittest.py deleted file mode 100644 index 0493c68..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/ospath_unittest.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org) -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Unit tests for ospath.py.""" - -import os -import unittest - -from webkitpy.common.system.ospath import relpath - - -# Make sure the tests in this class are platform independent. -class RelPathTest(unittest.TestCase): - - """Tests relpath().""" - - os_path_abspath = lambda self, path: path - - def _rel_path(self, path, abs_start_path): - return relpath(path, abs_start_path, self.os_path_abspath) - - def test_same_path(self): - rel_path = self._rel_path("WebKit", "WebKit") - self.assertEquals(rel_path, "") - - def test_long_rel_path(self): - start_path = "WebKit" - expected_rel_path = os.path.join("test", "Foo.txt") - path = os.path.join(start_path, expected_rel_path) - - rel_path = self._rel_path(path, start_path) - self.assertEquals(expected_rel_path, rel_path) - - def test_none_rel_path(self): - """Test _rel_path() with None return value.""" - start_path = "WebKit" - path = os.path.join("other_dir", "foo.txt") - - rel_path = self._rel_path(path, start_path) - self.assertTrue(rel_path is None) - - rel_path = self._rel_path("WebKitTools", "WebKit") - self.assertTrue(rel_path is None) diff --git a/WebKitTools/Scripts/webkitpy/common/system/outputcapture.py b/WebKitTools/Scripts/webkitpy/common/system/outputcapture.py deleted file mode 100644 index 45e0e3f..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/outputcapture.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright (c) 2009, Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# Class for unittest support. Used for capturing stderr/stdout. - -import sys -import unittest -from StringIO import StringIO - -class OutputCapture(object): - def __init__(self): - self.saved_outputs = dict() - - def _capture_output_with_name(self, output_name): - self.saved_outputs[output_name] = getattr(sys, output_name) - captured_output = StringIO() - setattr(sys, output_name, captured_output) - return captured_output - - def _restore_output_with_name(self, output_name): - captured_output = getattr(sys, output_name).getvalue() - setattr(sys, output_name, self.saved_outputs[output_name]) - del self.saved_outputs[output_name] - return captured_output - - def capture_output(self): - return (self._capture_output_with_name("stdout"), self._capture_output_with_name("stderr")) - - def restore_output(self): - return (self._restore_output_with_name("stdout"), self._restore_output_with_name("stderr")) - - def assert_outputs(self, testcase, function, args=[], kwargs={}, expected_stdout="", expected_stderr="", expected_exception=None): - self.capture_output() - if expected_exception: - return_value = testcase.assertRaises(expected_exception, function, *args, **kwargs) - else: - return_value = function(*args, **kwargs) - (stdout_string, stderr_string) = self.restore_output() - testcase.assertEqual(stdout_string, expected_stdout) - testcase.assertEqual(stderr_string, expected_stderr) - # This is a little strange, but I don't know where else to return this information. - return return_value - - -class OutputCaptureTestCaseBase(unittest.TestCase): - def setUp(self): - unittest.TestCase.setUp(self) - self.output_capture = OutputCapture() - (self.__captured_stdout, self.__captured_stderr) = self.output_capture.capture_output() - - def tearDown(self): - del self.__captured_stdout - del self.__captured_stderr - self.output_capture.restore_output() - unittest.TestCase.tearDown(self) - - def assertStdout(self, expected_stdout): - self.assertEquals(expected_stdout, self.__captured_stdout.getvalue()) - - def assertStderr(self, expected_stderr): - self.assertEquals(expected_stderr, self.__captured_stderr.getvalue()) diff --git a/WebKitTools/Scripts/webkitpy/common/system/path.py b/WebKitTools/Scripts/webkitpy/common/system/path.py deleted file mode 100644 index 09787d7..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/path.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""generic routines to convert platform-specific paths to URIs.""" -from __future__ import with_statement - -import atexit -import subprocess -import sys -import threading -import urllib - - -def abspath_to_uri(path, platform=None): - """Converts a platform-specific absolute path to a file: URL.""" - if platform is None: - platform = sys.platform - return "file:" + _escape(_convert_path(path, platform)) - - -def cygpath(path): - """Converts an absolute cygwin path to an absolute Windows path.""" - return _CygPath.convert_using_singleton(path) - - -# Note that this object is not threadsafe and must only be called -# from multiple threads under protection of a lock (as is done in cygpath()) -class _CygPath(object): - """Manages a long-running 'cygpath' process for file conversion.""" - _lock = None - _singleton = None - - @staticmethod - def stop_cygpath_subprocess(): - if not _CygPath._lock: - return - - with _CygPath._lock: - if _CygPath._singleton: - _CygPath._singleton.stop() - - @staticmethod - def convert_using_singleton(path): - if not _CygPath._lock: - _CygPath._lock = threading.Lock() - - with _CygPath._lock: - if not _CygPath._singleton: - _CygPath._singleton = _CygPath() - # Make sure the cygpath subprocess always gets shutdown cleanly. - atexit.register(_CygPath.stop_cygpath_subprocess) - - return _CygPath._singleton.convert(path) - - def __init__(self): - self._child_process = None - - def start(self): - assert(self._child_process is None) - args = ['cygpath', '-f', '-', '-wa'] - self._child_process = subprocess.Popen(args, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - - def is_running(self): - if not self._child_process: - return False - return self._child_process.returncode is None - - def stop(self): - if self._child_process: - self._child_process.stdin.close() - self._child_process.wait() - self._child_process = None - - def convert(self, path): - if not self.is_running(): - self.start() - self._child_process.stdin.write("%s\r\n" % path) - self._child_process.stdin.flush() - windows_path = self._child_process.stdout.readline().rstrip() - # Some versions of cygpath use lowercase drive letters while others - # use uppercase. We always convert to uppercase for consistency. - windows_path = '%s%s' % (windows_path[0].upper(), windows_path[1:]) - return windows_path - - -def _escape(path): - """Handle any characters in the path that should be escaped.""" - # FIXME: web browsers don't appear to blindly quote every character - # when converting filenames to files. Instead of using urllib's default - # rules, we allow a small list of other characters through un-escaped. - # It's unclear if this is the best possible solution. - return urllib.quote(path, safe='/+:') - - -def _convert_path(path, platform): - """Handles any os-specific path separators, mappings, etc.""" - if platform == 'win32': - return _winpath_to_uri(path) - if platform == 'cygwin': - return _winpath_to_uri(cygpath(path)) - return _unixypath_to_uri(path) - - -def _winpath_to_uri(path): - """Converts a window absolute path to a file: URL.""" - return "///" + path.replace("\\", "/") - - -def _unixypath_to_uri(path): - """Converts a unix-style path to a file: URL.""" - return "//" + path diff --git a/WebKitTools/Scripts/webkitpy/common/system/path_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/path_unittest.py deleted file mode 100644 index 4dbd38a..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/path_unittest.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest -import sys - -import path - -class AbspathTest(unittest.TestCase): - def assertMatch(self, test_path, expected_uri, - platform=None): - if platform == 'cygwin' and sys.platform != 'cygwin': - return - self.assertEqual(path.abspath_to_uri(test_path, platform=platform), - expected_uri) - - def test_abspath_to_uri_cygwin(self): - if sys.platform != 'cygwin': - return - - self.assertMatch('/cygdrive/c/foo/bar.html', - 'file:///C:/foo/bar.html', - platform='cygwin') - self.assertEqual(path.abspath_to_uri('/cygdrive/c/foo/bar.html', - platform='cygwin'), - 'file:///C:/foo/bar.html') - - def test_abspath_to_uri_darwin(self): - self.assertMatch('/foo/bar.html', - 'file:///foo/bar.html', - platform='darwin') - self.assertEqual(path.abspath_to_uri("/foo/bar.html", - platform='darwin'), - "file:///foo/bar.html") - - def test_abspath_to_uri_linux2(self): - self.assertMatch('/foo/bar.html', - 'file:///foo/bar.html', - platform='darwin') - self.assertEqual(path.abspath_to_uri("/foo/bar.html", - platform='linux2'), - "file:///foo/bar.html") - - def test_abspath_to_uri_win(self): - self.assertMatch('c:\\foo\\bar.html', - 'file:///c:/foo/bar.html', - platform='win32') - self.assertEqual(path.abspath_to_uri("c:\\foo\\bar.html", - platform='win32'), - "file:///c:/foo/bar.html") - - def test_abspath_to_uri_escaping(self): - self.assertMatch('/foo/bar + baz%?.html', - 'file:///foo/bar%20+%20baz%25%3F.html', - platform='darwin') - self.assertMatch('/foo/bar + baz%?.html', - 'file:///foo/bar%20+%20baz%25%3F.html', - platform='linux2') - - # Note that you can't have '?' in a filename on windows. - self.assertMatch('/cygdrive/c/foo/bar + baz%.html', - 'file:///C:/foo/bar%20+%20baz%25.html', - platform='cygwin') - - def test_stop_cygpath_subprocess(self): - if sys.platform != 'cygwin': - return - - # Call cygpath to ensure the subprocess is running. - path.cygpath("/cygdrive/c/foo.txt") - self.assertTrue(path._CygPath._singleton.is_running()) - - # Stop it. - path._CygPath.stop_cygpath_subprocess() - - # Ensure that it is stopped. - self.assertFalse(path._CygPath._singleton.is_running()) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/common/system/user.py b/WebKitTools/Scripts/webkitpy/common/system/user.py deleted file mode 100644 index 8917137..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/user.py +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright (c) 2009, Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging -import os -import re -import shlex -import subprocess -import sys -import webbrowser - - -_log = logging.getLogger("webkitpy.common.system.user") - - -try: - import readline -except ImportError: - if sys.platform != "win32": - # There is no readline module for win32, not much to do except cry. - _log.warn("Unable to import readline.") - # FIXME: We could give instructions for non-mac platforms. - # Lack of readline results in a very bad user experiance. - if sys.platform == "mac": - _log.warn("If you're using MacPorts, try running:") - _log.warn(" sudo port install py25-readline") - - -class User(object): - DEFAULT_NO = 'n' - DEFAULT_YES = 'y' - - # FIXME: These are @classmethods because bugzilla.py doesn't have a Tool object (thus no User instance). - @classmethod - def prompt(cls, message, repeat=1, raw_input=raw_input): - response = None - while (repeat and not response): - repeat -= 1 - response = raw_input(message) - return response - - @classmethod - def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=raw_input): - print list_title - i = 0 - for item in list_items: - i += 1 - print "%2d. %s" % (i, item) - - # Loop until we get valid input - while True: - if can_choose_multiple: - response = cls.prompt("Enter one or more numbers (comma-separated), or \"all\": ", raw_input=raw_input) - if not response.strip() or response == "all": - return list_items - try: - indices = [int(r) - 1 for r in re.split("\s*,\s*", response)] - except ValueError, err: - continue - return [list_items[i] for i in indices] - else: - try: - result = int(cls.prompt("Enter a number: ", raw_input=raw_input)) - 1 - except ValueError, err: - continue - return list_items[result] - - def edit(self, files): - editor = os.environ.get("EDITOR") or "vi" - args = shlex.split(editor) - # Note: Not thread safe: http://bugs.python.org/issue2320 - subprocess.call(args + files) - - def _warn_if_application_is_xcode(self, edit_application): - if "Xcode" in edit_application: - print "Instead of using Xcode.app, consider using EDITOR=\"xed --wait\"." - - def edit_changelog(self, files): - edit_application = os.environ.get("CHANGE_LOG_EDIT_APPLICATION") - if edit_application and sys.platform == "darwin": - # On Mac we support editing ChangeLogs using an application. - args = shlex.split(edit_application) - print "Using editor in the CHANGE_LOG_EDIT_APPLICATION environment variable." - print "Please quit the editor application when done editing." - self._warn_if_application_is_xcode(edit_application) - subprocess.call(["open", "-W", "-n", "-a"] + args + files) - return - self.edit(files) - - def page(self, message): - pager = os.environ.get("PAGER") or "less" - try: - # Note: Not thread safe: http://bugs.python.org/issue2320 - child_process = subprocess.Popen([pager], stdin=subprocess.PIPE) - child_process.communicate(input=message) - except IOError, e: - pass - - def confirm(self, message=None, default=DEFAULT_YES, raw_input=raw_input): - if not message: - message = "Continue?" - choice = {'y': 'Y/n', 'n': 'y/N'}[default] - response = raw_input("%s [%s]: " % (message, choice)) - if not response: - response = default - return response.lower() == 'y' - - def can_open_url(self): - try: - webbrowser.get() - return True - except webbrowser.Error, e: - return False - - def open_url(self, url): - if not self.can_open_url(): - _log.warn("Failed to open %s" % url) - webbrowser.open(url) diff --git a/WebKitTools/Scripts/webkitpy/common/system/user_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/user_unittest.py deleted file mode 100644 index 7ec9b34..0000000 --- a/WebKitTools/Scripts/webkitpy/common/system/user_unittest.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright (C) 2010 Research in Motion Ltd. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Research in Motion Ltd. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.common.system.user import User - -class UserTest(unittest.TestCase): - - example_user_response = "example user response" - - def test_prompt_repeat(self): - self.repeatsRemaining = 2 - def mock_raw_input(message): - self.repeatsRemaining -= 1 - if not self.repeatsRemaining: - return UserTest.example_user_response - return None - self.assertEqual(User.prompt("input", repeat=self.repeatsRemaining, raw_input=mock_raw_input), UserTest.example_user_response) - - def test_prompt_when_exceeded_repeats(self): - self.repeatsRemaining = 2 - def mock_raw_input(message): - self.repeatsRemaining -= 1 - return None - self.assertEqual(User.prompt("input", repeat=self.repeatsRemaining, raw_input=mock_raw_input), None) - - def test_prompt_with_list(self): - def run_prompt_test(inputs, expected_result, can_choose_multiple=False): - def mock_raw_input(message): - return inputs.pop(0) - output_capture = OutputCapture() - actual_result = output_capture.assert_outputs( - self, - User.prompt_with_list, - args=["title", ["foo", "bar"]], - kwargs={"can_choose_multiple": can_choose_multiple, "raw_input": mock_raw_input}, - expected_stdout="title\n 1. foo\n 2. bar\n") - self.assertEqual(actual_result, expected_result) - self.assertEqual(len(inputs), 0) - - run_prompt_test(["1"], "foo") - run_prompt_test(["badinput", "2"], "bar") - - run_prompt_test(["1,2"], ["foo", "bar"], can_choose_multiple=True) - run_prompt_test([" 1, 2 "], ["foo", "bar"], can_choose_multiple=True) - run_prompt_test(["all"], ["foo", "bar"], can_choose_multiple=True) - run_prompt_test([""], ["foo", "bar"], can_choose_multiple=True) - run_prompt_test([" "], ["foo", "bar"], can_choose_multiple=True) - run_prompt_test(["badinput", "all"], ["foo", "bar"], can_choose_multiple=True) - - def test_confirm(self): - test_cases = ( - (("Continue? [Y/n]: ", True), (User.DEFAULT_YES, 'y')), - (("Continue? [Y/n]: ", False), (User.DEFAULT_YES, 'n')), - (("Continue? [Y/n]: ", True), (User.DEFAULT_YES, '')), - (("Continue? [Y/n]: ", False), (User.DEFAULT_YES, 'q')), - (("Continue? [y/N]: ", True), (User.DEFAULT_NO, 'y')), - (("Continue? [y/N]: ", False), (User.DEFAULT_NO, 'n')), - (("Continue? [y/N]: ", False), (User.DEFAULT_NO, '')), - (("Continue? [y/N]: ", False), (User.DEFAULT_NO, 'q')), - ) - for test_case in test_cases: - expected, inputs = test_case - - def mock_raw_input(message): - self.assertEquals(expected[0], message) - return inputs[1] - - result = User().confirm(default=inputs[0], - raw_input=mock_raw_input) - self.assertEquals(expected[1], result) - - def test_warn_if_application_is_xcode(self): - output = OutputCapture() - user = User() - output.assert_outputs(self, user._warn_if_application_is_xcode, ["TextMate"]) - output.assert_outputs(self, user._warn_if_application_is_xcode, ["/Applications/TextMate.app"]) - output.assert_outputs(self, user._warn_if_application_is_xcode, ["XCode"]) # case sensitive matching - - xcode_warning = "Instead of using Xcode.app, consider using EDITOR=\"xed --wait\".\n" - output.assert_outputs(self, user._warn_if_application_is_xcode, ["Xcode"], expected_stdout=xcode_warning) - output.assert_outputs(self, user._warn_if_application_is_xcode, ["/Developer/Applications/Xcode.app"], expected_stdout=xcode_warning) diff --git a/WebKitTools/Scripts/webkitpy/common/thread/__init__.py b/WebKitTools/Scripts/webkitpy/common/thread/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/common/thread/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/common/thread/messagepump.py b/WebKitTools/Scripts/webkitpy/common/thread/messagepump.py deleted file mode 100644 index 0e39285..0000000 --- a/WebKitTools/Scripts/webkitpy/common/thread/messagepump.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -class MessagePumpDelegate(object): - def schedule(self, interval, callback): - raise NotImplementedError, "subclasses must implement" - - def message_available(self, message): - raise NotImplementedError, "subclasses must implement" - - def final_message_delivered(self): - raise NotImplementedError, "subclasses must implement" - - -class MessagePump(object): - interval = 10 # seconds - - def __init__(self, delegate, message_queue): - self._delegate = delegate - self._message_queue = message_queue - self._schedule() - - def _schedule(self): - self._delegate.schedule(self.interval, self._callback) - - def _callback(self): - (messages, is_running) = self._message_queue.take_all() - for message in messages: - self._delegate.message_available(message) - if not is_running: - self._delegate.final_message_delivered() - return - self._schedule() diff --git a/WebKitTools/Scripts/webkitpy/common/thread/messagepump_unittest.py b/WebKitTools/Scripts/webkitpy/common/thread/messagepump_unittest.py deleted file mode 100644 index f731db2..0000000 --- a/WebKitTools/Scripts/webkitpy/common/thread/messagepump_unittest.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.thread.messagepump import MessagePump, MessagePumpDelegate -from webkitpy.common.thread.threadedmessagequeue import ThreadedMessageQueue - - -class TestDelegate(MessagePumpDelegate): - def __init__(self): - self.log = [] - - def schedule(self, interval, callback): - self.callback = callback - self.log.append("schedule") - - def message_available(self, message): - self.log.append("message_available: %s" % message) - - def final_message_delivered(self): - self.log.append("final_message_delivered") - - -class MessagePumpTest(unittest.TestCase): - - def test_basic(self): - queue = ThreadedMessageQueue() - delegate = TestDelegate() - pump = MessagePump(delegate, queue) - self.assertEqual(delegate.log, [ - 'schedule' - ]) - delegate.callback() - queue.post("Hello") - queue.post("There") - delegate.callback() - self.assertEqual(delegate.log, [ - 'schedule', - 'schedule', - 'message_available: Hello', - 'message_available: There', - 'schedule' - ]) - queue.post("More") - queue.post("Messages") - queue.stop() - delegate.callback() - self.assertEqual(delegate.log, [ - 'schedule', - 'schedule', - 'message_available: Hello', - 'message_available: There', - 'schedule', - 'message_available: More', - 'message_available: Messages', - 'final_message_delivered' - ]) diff --git a/WebKitTools/Scripts/webkitpy/common/thread/threadedmessagequeue.py b/WebKitTools/Scripts/webkitpy/common/thread/threadedmessagequeue.py deleted file mode 100644 index 17b6277..0000000 --- a/WebKitTools/Scripts/webkitpy/common/thread/threadedmessagequeue.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from __future__ import with_statement - -import threading - - -class ThreadedMessageQueue(object): - def __init__(self): - self._messages = [] - self._is_running = True - self._lock = threading.Lock() - - def post(self, message): - with self._lock: - self._messages.append(message) - - def stop(self): - with self._lock: - self._is_running = False - - def take_all(self): - with self._lock: - messages = self._messages - is_running = self._is_running - self._messages = [] - return (messages, is_running) - diff --git a/WebKitTools/Scripts/webkitpy/common/thread/threadedmessagequeue_unittest.py b/WebKitTools/Scripts/webkitpy/common/thread/threadedmessagequeue_unittest.py deleted file mode 100644 index cb67c1e..0000000 --- a/WebKitTools/Scripts/webkitpy/common/thread/threadedmessagequeue_unittest.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unittest - -from webkitpy.common.thread.threadedmessagequeue import ThreadedMessageQueue - -class ThreadedMessageQueueTest(unittest.TestCase): - - def test_basic(self): - queue = ThreadedMessageQueue() - queue.post("Hello") - queue.post("There") - (messages, is_running) = queue.take_all() - self.assertEqual(messages, ["Hello", "There"]) - self.assertTrue(is_running) - (messages, is_running) = queue.take_all() - self.assertEqual(messages, []) - self.assertTrue(is_running) - queue.post("More") - queue.stop() - queue.post("Messages") - (messages, is_running) = queue.take_all() - self.assertEqual(messages, ["More", "Messages"]) - self.assertFalse(is_running) - (messages, is_running) = queue.take_all() - self.assertEqual(messages, []) - self.assertFalse(is_running) |