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 | |
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')
298 files changed, 0 insertions, 55175 deletions
diff --git a/WebKitTools/Scripts/webkitpy/__init__.py b/WebKitTools/Scripts/webkitpy/__init__.py deleted file mode 100644 index b376bf2..0000000 --- a/WebKitTools/Scripts/webkitpy/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Required for Python to search this directory for module files - -# Keep this file free of any code or import statements that could -# cause either an error to occur or a log message to be logged. -# This ensures that calling code can import initialization code from -# webkitpy before any errors or log messages due to code in this file. -# Initialization code can include things like version-checking code and -# logging configuration code. -# -# We do not execute any version-checking code or logging configuration -# code in this file so that callers can opt-in as they want. This also -# allows different callers to choose different initialization code, -# as necessary. 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) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/__init__.py b/WebKitTools/Scripts/webkitpy/layout_tests/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py deleted file mode 100644 index 51dcac8..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests.py +++ /dev/null @@ -1,231 +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: -# -# 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 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 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. - -"""deduplicate_tests -- lists duplicated between platforms. - -If platform/mac-leopard is missing an expected test output, we fall back on -platform/mac. This means it's possible to grow redundant test outputs, -where we have the same expected data in both a platform directory and another -platform it falls back on. -""" - -import collections -import fnmatch -import os -import subprocess -import sys -import re -import webkitpy.common.checkout.scm as scm -import webkitpy.common.system.executive as executive -import webkitpy.common.system.logutils as logutils -import webkitpy.common.system.ospath as ospath -import webkitpy.layout_tests.port.factory as port_factory - -_log = logutils.get_logger(__file__) - -_BASE_PLATFORM = 'base' - - -def port_fallbacks(): - """Get the port fallback information. - Returns: - A dictionary mapping platform name to a list of other platforms to fall - back on. All platforms fall back on 'base'. - """ - fallbacks = {_BASE_PLATFORM: []} - platform_dir = os.path.join(scm.find_checkout_root(), 'LayoutTests', - 'platform') - for port_name in os.listdir(platform_dir): - try: - platforms = port_factory.get(port_name).baseline_search_path() - except NotImplementedError: - _log.error("'%s' lacks baseline_search_path(), please fix." - % port_name) - fallbacks[port_name] = [_BASE_PLATFORM] - continue - fallbacks[port_name] = [os.path.basename(p) for p in platforms][1:] - fallbacks[port_name].append(_BASE_PLATFORM) - return fallbacks - - -def parse_git_output(git_output, glob_pattern): - """Parses the output of git ls-tree and filters based on glob_pattern. - Args: - git_output: result of git ls-tree -r HEAD LayoutTests. - glob_pattern: a pattern to filter the files. - Returns: - A dictionary mapping (test name, hash of content) => [paths] - """ - hashes = collections.defaultdict(set) - for line in git_output.split('\n'): - if not line: - break - attrs, path = line.strip().split('\t') - if not fnmatch.fnmatch(path, glob_pattern): - continue - path = path[len('LayoutTests/'):] - match = re.match(r'^(platform/.*?/)?(.*)', path) - test = match.group(2) - _, _, hash = attrs.split(' ') - hashes[(test, hash)].add(path) - return hashes - - -def cluster_file_hashes(glob_pattern): - """Get the hashes of all the test expectations in the tree. - We cheat and use git's hashes. - Args: - glob_pattern: a pattern to filter the files. - Returns: - A dictionary mapping (test name, hash of content) => [paths] - """ - - # A map of file hash => set of all files with that hash. - hashes = collections.defaultdict(set) - - # Fill in the map. - cmd = ('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests') - try: - git_output = executive.Executive().run_command(cmd, - cwd=scm.find_checkout_root()) - except OSError, e: - if e.errno == 2: # No such file or directory. - _log.error("Error: 'No such file' when running git.") - _log.error("This script requires git.") - sys.exit(1) - raise e - return parse_git_output(git_output, glob_pattern) - - -def extract_platforms(paths): - """Extracts the platforms from a list of paths matching ^platform/(.*?)/. - Args: - paths: a list of paths. - Returns: - A dictionary containing all platforms from paths. - """ - platforms = {} - for path in paths: - match = re.match(r'^platform/(.*?)/', path) - if match: - platform = match.group(1) - else: - platform = _BASE_PLATFORM - platforms[platform] = path - return platforms - - -def has_intermediate_results(test, fallbacks, matching_platform, - path_exists=os.path.exists): - """Returns True if there is a test result that causes us to not delete - this duplicate. - - For example, chromium-linux may be a duplicate of the checked in result, - but chromium-win may have a different result checked in. In this case, - we need to keep the duplicate results. - - Args: - test: The test name. - fallbacks: A list of platforms we fall back on. - matching_platform: The platform that we found the duplicate test - result. We can stop checking here. - path_exists: Optional parameter that allows us to stub out - os.path.exists for testing. - """ - for platform in fallbacks: - if platform == matching_platform: - return False - test_path = os.path.join('LayoutTests', 'platform', platform, test) - if path_exists(test_path): - return True - return False - - -def get_relative_test_path(filename, relative_to, - checkout_root=scm.find_checkout_root()): - """Constructs a relative path to |filename| from |relative_to|. - Args: - filename: The test file we're trying to get a relative path to. - relative_to: The absolute path we're relative to. - Returns: - A relative path to filename or None if |filename| is not below - |relative_to|. - """ - layout_test_dir = os.path.join(checkout_root, 'LayoutTests') - abs_path = os.path.join(layout_test_dir, filename) - return ospath.relpath(abs_path, relative_to) - - -def find_dups(hashes, port_fallbacks, relative_to): - """Yields info about redundant test expectations. - Args: - hashes: a list of hashes as returned by cluster_file_hashes. - port_fallbacks: a list of fallback information as returned by - get_port_fallbacks. - relative_to: the directory that we want the results relative to - Returns: - a tuple containing (test, platform, fallback, platforms) - """ - for (test, hash), cluster in hashes.items(): - if len(cluster) < 2: - continue # Common case: only one file with that hash. - - # Compute the list of platforms we have this particular hash for. - platforms = extract_platforms(cluster) - if len(platforms) == 1: - continue - - # See if any of the platforms are redundant with each other. - for platform in platforms.keys(): - for fallback in port_fallbacks[platform]: - if fallback not in platforms.keys(): - continue - # We have to verify that there isn't an intermediate result - # that causes this duplicate hash to exist. - if has_intermediate_results(test, port_fallbacks[platform], - fallback): - continue - # We print the relative path so it's easy to pipe the results - # to xargs rm. - path = get_relative_test_path(platforms[platform], relative_to) - if not path: - continue - yield { - 'test': test, - 'platform': platform, - 'fallback': fallback, - 'path': path, - } - - -def deduplicate(glob_pattern): - """Traverses LayoutTests and returns information about duplicated files. - Args: - glob pattern to filter the files in LayoutTests. - Returns: - a dictionary containing test, path, platform and fallback. - """ - fallbacks = port_fallbacks() - hashes = cluster_file_hashes(glob_pattern) - return list(find_dups(hashes, fallbacks, os.getcwd())) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py deleted file mode 100644 index 309bf8d..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/deduplicate_tests_unittest.py +++ /dev/null @@ -1,210 +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: -# -# 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 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 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 deduplicate_tests.py.""" - -import deduplicate_tests -import os -import unittest -import webkitpy.common.checkout.scm as scm - - -class MockExecutive(object): - last_run_command = [] - response = '' - - class Executive(object): - def run_command(self, - args, - cwd=None, - input=None, - error_handler=None, - return_exit_code=False, - return_stderr=True, - decode_output=True): - MockExecutive.last_run_command += [args] - return MockExecutive.response - - -class ListDuplicatesTest(unittest.TestCase): - def setUp(self): - MockExecutive.last_run_command = [] - MockExecutive.response = '' - deduplicate_tests.executive = MockExecutive - self._original_cwd = os.getcwd() - checkout_root = scm.find_checkout_root() - self.assertNotEqual(checkout_root, None) - os.chdir(checkout_root) - - def tearDown(self): - os.chdir(self._original_cwd) - - def test_parse_git_output(self): - git_output = ( - '100644 blob 5053240b3353f6eb39f7cb00259785f16d121df2\tLayoutTests/mac/foo-expected.txt\n' - '100644 blob a004548d107ecc4e1ea08019daf0a14e8634a1ff\tLayoutTests/platform/chromium/foo-expected.txt\n' - '100644 blob d6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-linux/foo-expected.txt\n' - '100644 blob abcdebc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-linux/animage.png\n' - '100644 blob d6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-win/foo-expected.txt\n' - '100644 blob abcdebc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-win/animage.png\n' - '100644 blob 4303df5389ca87cae83dd3236b8dd84e16606517\tLayoutTests/platform/mac/foo-expected.txt\n') - hashes = deduplicate_tests.parse_git_output(git_output, '*') - expected = {('mac/foo-expected.txt', '5053240b3353f6eb39f7cb00259785f16d121df2'): set(['mac/foo-expected.txt']), - ('animage.png', 'abcdebc762e3aec5df03b5c04485b2cb3b65ffb1'): set(['platform/chromium-linux/animage.png', 'platform/chromium-win/animage.png']), - ('foo-expected.txt', '4303df5389ca87cae83dd3236b8dd84e16606517'): set(['platform/mac/foo-expected.txt']), - ('foo-expected.txt', 'd6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1'): set(['platform/chromium-linux/foo-expected.txt', 'platform/chromium-win/foo-expected.txt']), - ('foo-expected.txt', 'a004548d107ecc4e1ea08019daf0a14e8634a1ff'): set(['platform/chromium/foo-expected.txt'])} - self.assertEquals(expected, hashes) - - hashes = deduplicate_tests.parse_git_output(git_output, '*.png') - expected = {('animage.png', 'abcdebc762e3aec5df03b5c04485b2cb3b65ffb1'): set(['platform/chromium-linux/animage.png', 'platform/chromium-win/animage.png'])} - self.assertEquals(expected, hashes) - - def test_extract_platforms(self): - self.assertEquals({'foo': 'platform/foo/bar', - 'zoo': 'platform/zoo/com'}, - deduplicate_tests.extract_platforms(['platform/foo/bar', 'platform/zoo/com'])) - self.assertEquals({'foo': 'platform/foo/bar', - deduplicate_tests._BASE_PLATFORM: 'what/'}, - deduplicate_tests.extract_platforms(['platform/foo/bar', 'what/'])) - - def test_has_intermediate_results(self): - test_cases = ( - # If we found a duplicate in our first fallback, we have no - # intermediate results. - (False, ('fast/foo-expected.txt', - ['chromium-win', 'chromium', 'base'], - 'chromium-win', - lambda path: True)), - # Since chromium-win has a result, we have an intermediate result. - (True, ('fast/foo-expected.txt', - ['chromium-win', 'chromium', 'base'], - 'chromium', - lambda path: True)), - # There are no intermediate results. - (False, ('fast/foo-expected.txt', - ['chromium-win', 'chromium', 'base'], - 'chromium', - lambda path: False)), - # There are no intermediate results since a result for chromium is - # our duplicate file. - (False, ('fast/foo-expected.txt', - ['chromium-win', 'chromium', 'base'], - 'chromium', - lambda path: path == 'LayoutTests/platform/chromium/fast/foo-expected.txt')), - # We have an intermediate result in 'chromium' even though our - # duplicate is with the file in 'base'. - (True, ('fast/foo-expected.txt', - ['chromium-win', 'chromium', 'base'], - 'base', - lambda path: path == 'LayoutTests/platform/chromium/fast/foo-expected.txt')), - # We have an intermediate result in 'chromium-win' even though our - # duplicate is in 'base'. - (True, ('fast/foo-expected.txt', - ['chromium-win', 'chromium', 'base'], - 'base', - lambda path: path == 'LayoutTests/platform/chromium-win/fast/foo-expected.txt')), - ) - for expected, inputs in test_cases: - self.assertEquals(expected, - deduplicate_tests.has_intermediate_results(*inputs)) - - def test_unique(self): - MockExecutive.response = ( - '100644 blob 5053240b3353f6eb39f7cb00259785f16d121df2\tLayoutTests/mac/foo-expected.txt\n' - '100644 blob a004548d107ecc4e1ea08019daf0a14e8634a1ff\tLayoutTests/platform/chromium/foo-expected.txt\n' - '100644 blob abcd0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-linux/foo-expected.txt\n' - '100644 blob d6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-win/foo-expected.txt\n' - '100644 blob 4303df5389ca87cae83dd3236b8dd84e16606517\tLayoutTests/platform/mac/foo-expected.txt\n') - result = deduplicate_tests.deduplicate('*') - self.assertEquals(1, len(MockExecutive.last_run_command)) - self.assertEquals(('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests'), MockExecutive.last_run_command[-1]) - self.assertEquals(0, len(result)) - - def test_duplicates(self): - MockExecutive.response = ( - '100644 blob 5053240b3353f6eb39f7cb00259785f16d121df2\tLayoutTests/mac/foo-expected.txt\n' - '100644 blob a004548d107ecc4e1ea08019daf0a14e8634a1ff\tLayoutTests/platform/chromium/foo-expected.txt\n' - '100644 blob d6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-linux/foo-expected.txt\n' - '100644 blob abcdebc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-linux/animage.png\n' - '100644 blob d6bb0bc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-win/foo-expected.txt\n' - '100644 blob abcdebc762e3aec5df03b5c04485b2cb3b65ffb1\tLayoutTests/platform/chromium-win/animage.png\n' - '100644 blob 4303df5389ca87cae83dd3236b8dd84e16606517\tLayoutTests/platform/mac/foo-expected.txt\n') - - result = deduplicate_tests.deduplicate('*') - self.assertEquals(1, len(MockExecutive.last_run_command)) - self.assertEquals(('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests'), MockExecutive.last_run_command[-1]) - self.assertEquals(2, len(result)) - self.assertEquals({'test': 'animage.png', - 'path': 'LayoutTests/platform/chromium-linux/animage.png', - 'fallback': 'chromium-win', - 'platform': 'chromium-linux'}, - result[0]) - self.assertEquals({'test': 'foo-expected.txt', - 'path': 'LayoutTests/platform/chromium-linux/foo-expected.txt', - 'fallback': 'chromium-win', - 'platform': 'chromium-linux'}, - result[1]) - - result = deduplicate_tests.deduplicate('*.txt') - self.assertEquals(2, len(MockExecutive.last_run_command)) - self.assertEquals(('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests'), MockExecutive.last_run_command[-1]) - self.assertEquals(1, len(result)) - self.assertEquals({'test': 'foo-expected.txt', - 'path': 'LayoutTests/platform/chromium-linux/foo-expected.txt', - 'fallback': 'chromium-win', - 'platform': 'chromium-linux'}, - result[0]) - - result = deduplicate_tests.deduplicate('*.png') - self.assertEquals(3, len(MockExecutive.last_run_command)) - self.assertEquals(('git', 'ls-tree', '-r', 'HEAD', 'LayoutTests'), MockExecutive.last_run_command[-1]) - self.assertEquals(1, len(result)) - self.assertEquals({'test': 'animage.png', - 'path': 'LayoutTests/platform/chromium-linux/animage.png', - 'fallback': 'chromium-win', - 'platform': 'chromium-linux'}, - result[0]) - - def test_get_relative_test_path(self): - checkout_root = scm.find_checkout_root() - layout_test_dir = os.path.join(checkout_root, 'LayoutTests') - test_cases = ( - ('platform/mac/test.html', - ('platform/mac/test.html', layout_test_dir)), - ('LayoutTests/platform/mac/test.html', - ('platform/mac/test.html', checkout_root)), - (None, - ('platform/mac/test.html', os.path.join(checkout_root, 'WebCore'))), - ('test.html', - ('platform/mac/test.html', os.path.join(layout_test_dir, 'platform/mac'))), - (None, - ('platform/mac/test.html', os.path.join(layout_test_dir, 'platform/win'))), - ) - for expected, inputs in test_cases: - self.assertEquals(expected, - deduplicate_tests.get_relative_test_path(*inputs)) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/__init__.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/__init__.py +++ /dev/null diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py deleted file mode 100644 index fdb8da6..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/dump_render_tree_thread.py +++ /dev/null @@ -1,569 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Google Inc. All rights reserved. -# Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged -# -# 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 Thread object for running DumpRenderTree and processing URLs from a -shared queue. - -Each thread runs a separate instance of the DumpRenderTree binary and validates -the output. When there are no more URLs to process in the shared queue, the -thread exits. -""" - -from __future__ import with_statement - -import codecs -import copy -import logging -import os -import Queue -import signal -import sys -import thread -import threading -import time - - -from webkitpy.layout_tests.test_types import image_diff -from webkitpy.layout_tests.test_types import test_type_base -from webkitpy.layout_tests.test_types import text_diff - -import test_failures -import test_output -import test_results - -_log = logging.getLogger("webkitpy.layout_tests.layout_package." - "dump_render_tree_thread") - - -def _expected_test_output(port, filename): - """Returns an expected TestOutput object.""" - return test_output.TestOutput(port.expected_text(filename), - port.expected_image(filename), - port.expected_checksum(filename)) - -def _process_output(port, options, test_input, test_types, test_args, - test_output, worker_name): - """Receives the output from a DumpRenderTree process, subjects it to a - number of tests, and returns a list of failure types the test produced. - - Args: - port: port-specific hooks - options: command line options argument from optparse - proc: an active DumpRenderTree process - test_input: Object containing the test filename and timeout - test_types: list of test types to subject the output to - test_args: arguments to be passed to each test - test_output: a TestOutput object containing the output of the test - worker_name: worker name for logging - - Returns: a TestResult object - """ - failures = [] - - if test_output.crash: - failures.append(test_failures.FailureCrash()) - if test_output.timeout: - failures.append(test_failures.FailureTimeout()) - - test_name = port.relative_test_filename(test_input.filename) - if test_output.crash: - _log.debug("%s Stacktrace for %s:\n%s" % (worker_name, test_name, - test_output.error)) - filename = os.path.join(options.results_directory, test_name) - filename = os.path.splitext(filename)[0] + "-stack.txt" - port.maybe_make_directory(os.path.split(filename)[0]) - with codecs.open(filename, "wb", "utf-8") as file: - file.write(test_output.error) - elif test_output.error: - _log.debug("%s %s output stderr lines:\n%s" % (worker_name, test_name, - test_output.error)) - - expected_test_output = _expected_test_output(port, test_input.filename) - - # Check the output and save the results. - start_time = time.time() - time_for_diffs = {} - for test_type in test_types: - start_diff_time = time.time() - new_failures = test_type.compare_output(port, test_input.filename, - test_args, test_output, - expected_test_output) - # Don't add any more failures if we already have a crash, so we don't - # double-report those tests. We do double-report for timeouts since - # we still want to see the text and image output. - if not test_output.crash: - failures.extend(new_failures) - time_for_diffs[test_type.__class__.__name__] = ( - time.time() - start_diff_time) - - total_time_for_all_diffs = time.time() - start_diff_time - return test_results.TestResult(test_input.filename, failures, test_output.test_time, - total_time_for_all_diffs, time_for_diffs) - - -def _pad_timeout(timeout): - """Returns a safe multiple of the per-test timeout value to use - to detect hung test threads. - - """ - # When we're running one test per DumpRenderTree process, we can - # enforce a hard timeout. The DumpRenderTree watchdog uses 2.5x - # the timeout; we want to be larger than that. - return timeout * 3 - - -def _milliseconds_to_seconds(msecs): - return float(msecs) / 1000.0 - - -def _should_fetch_expected_checksum(options): - return options.pixel_tests and not (options.new_baseline or options.reset_results) - - -def _run_single_test(port, options, test_input, test_types, test_args, driver, worker_name): - # FIXME: Pull this into TestShellThread._run(). - - # The image hash is used to avoid doing an image dump if the - # checksums match, so it should be set to a blank value if we - # are generating a new baseline. (Otherwise, an image from a - # previous run will be copied into the baseline.""" - if _should_fetch_expected_checksum(options): - test_input.image_hash = port.expected_checksum(test_input.filename) - test_output = driver.run_test(test_input) - return _process_output(port, options, test_input, test_types, test_args, - test_output, worker_name) - - -class SingleTestThread(threading.Thread): - """Thread wrapper for running a single test file.""" - - def __init__(self, port, options, worker_number, worker_name, - test_input, test_types, test_args): - """ - Args: - port: object implementing port-specific hooks - options: command line argument object from optparse - worker_number: worker number for tests - worker_name: for logging - test_input: Object containing the test filename and timeout - test_types: A list of TestType objects to run the test output - against. - test_args: A TestArguments object to pass to each TestType. - """ - - threading.Thread.__init__(self) - self._port = port - self._options = options - self._test_input = test_input - self._test_types = test_types - self._test_args = test_args - self._driver = None - self._worker_number = worker_number - self._name = worker_name - - def run(self): - self._covered_run() - - def _covered_run(self): - # FIXME: this is a separate routine to work around a bug - # in coverage: see http://bitbucket.org/ned/coveragepy/issue/85. - self._driver = self._port.create_driver(self._worker_number) - self._driver.start() - self._test_result = _run_single_test(self._port, self._options, - self._test_input, self._test_types, - self._test_args, self._driver, - self._name) - self._driver.stop() - - def get_test_result(self): - return self._test_result - - -class WatchableThread(threading.Thread): - """This class abstracts an interface used by - run_webkit_tests.TestRunner._wait_for_threads_to_finish for thread - management.""" - def __init__(self): - threading.Thread.__init__(self) - self._canceled = False - self._exception_info = None - self._next_timeout = None - self._thread_id = None - - def cancel(self): - """Set a flag telling this thread to quit.""" - self._canceled = True - - def clear_next_timeout(self): - """Mark a flag telling this thread to stop setting timeouts.""" - self._timeout = 0 - - def exception_info(self): - """If run() terminated on an uncaught exception, return it here - ((type, value, traceback) tuple). - Returns None if run() terminated normally. Meant to be called after - joining this thread.""" - return self._exception_info - - def id(self): - """Return a thread identifier.""" - return self._thread_id - - def next_timeout(self): - """Return the time the test is supposed to finish by.""" - return self._next_timeout - - -class TestShellThread(WatchableThread): - def __init__(self, port, options, worker_number, worker_name, - filename_list_queue, result_queue): - """Initialize all the local state for this DumpRenderTree thread. - - Args: - port: interface to port-specific hooks - options: command line options argument from optparse - worker_number: identifier for a particular worker thread. - worker_name: for logging. - filename_list_queue: A thread safe Queue class that contains lists - of tuples of (filename, uri) pairs. - result_queue: A thread safe Queue class that will contain - serialized TestResult objects. - """ - WatchableThread.__init__(self) - self._port = port - self._options = options - self._worker_number = worker_number - self._name = worker_name - self._filename_list_queue = filename_list_queue - self._result_queue = result_queue - self._filename_list = [] - self._driver = None - self._test_group_timing_stats = {} - self._test_results = [] - self._num_tests = 0 - self._start_time = 0 - self._stop_time = 0 - self._have_http_lock = False - self._http_lock_wait_begin = 0 - self._http_lock_wait_end = 0 - - self._test_types = [] - for cls in self._get_test_type_classes(): - self._test_types.append(cls(self._port, - self._options.results_directory)) - self._test_args = self._get_test_args(worker_number) - - # Current group of tests we're running. - self._current_group = None - # Number of tests in self._current_group. - self._num_tests_in_current_group = None - # Time at which we started running tests from self._current_group. - self._current_group_start_time = None - - def _get_test_args(self, worker_number): - """Returns the tuple of arguments for tests and for DumpRenderTree.""" - test_args = test_type_base.TestArguments() - test_args.new_baseline = self._options.new_baseline - test_args.reset_results = self._options.reset_results - - return test_args - - def _get_test_type_classes(self): - classes = [text_diff.TestTextDiff] - if self._options.pixel_tests: - classes.append(image_diff.ImageDiff) - return classes - - def get_test_group_timing_stats(self): - """Returns a dictionary mapping test group to a tuple of - (number of tests in that group, time to run the tests)""" - return self._test_group_timing_stats - - def get_test_results(self): - """Return the list of all tests run on this thread. - - This is used to calculate per-thread statistics. - - """ - return self._test_results - - def get_total_time(self): - return max(self._stop_time - self._start_time - - self._http_lock_wait_time(), 0.0) - - def get_num_tests(self): - return self._num_tests - - def run(self): - """Delegate main work to a helper method and watch for uncaught - exceptions.""" - self._covered_run() - - def _covered_run(self): - # FIXME: this is a separate routine to work around a bug - # in coverage: see http://bitbucket.org/ned/coveragepy/issue/85. - self._thread_id = thread.get_ident() - self._start_time = time.time() - self._num_tests = 0 - try: - _log.debug('%s starting' % (self.getName())) - self._run(test_runner=None, result_summary=None) - _log.debug('%s done (%d tests)' % (self.getName(), - self.get_num_tests())) - except KeyboardInterrupt: - self._exception_info = sys.exc_info() - _log.debug("%s interrupted" % self.getName()) - except: - # Save the exception for our caller to see. - self._exception_info = sys.exc_info() - self._stop_time = time.time() - _log.error('%s dying, exception raised' % self.getName()) - - self._stop_time = time.time() - - def run_in_main_thread(self, test_runner, result_summary): - """This hook allows us to run the tests from the main thread if - --num-test-shells==1, instead of having to always run two or more - threads. This allows us to debug the test harness without having to - do multi-threaded debugging.""" - self._run(test_runner, result_summary) - - def cancel(self): - """Clean up http lock and set a flag telling this thread to quit.""" - self._stop_servers_with_lock() - WatchableThread.cancel(self) - - def next_timeout(self): - """Return the time the test is supposed to finish by.""" - if self._next_timeout: - return self._next_timeout + self._http_lock_wait_time() - return self._next_timeout - - def _http_lock_wait_time(self): - """Return the time what http locking takes.""" - if self._http_lock_wait_begin == 0: - return 0 - if self._http_lock_wait_end == 0: - return time.time() - self._http_lock_wait_begin - return self._http_lock_wait_end - self._http_lock_wait_begin - - def _run(self, test_runner, result_summary): - """Main work entry point of the thread. Basically we pull urls from the - filename queue and run the tests until we run out of urls. - - If test_runner is not None, then we call test_runner.UpdateSummary() - with the results of each test.""" - batch_size = self._options.batch_size - batch_count = 0 - - # Append tests we're running to the existing tests_run.txt file. - # This is created in run_webkit_tests.py:_PrepareListsAndPrintOutput. - tests_run_filename = os.path.join(self._options.results_directory, - "tests_run.txt") - tests_run_file = codecs.open(tests_run_filename, "a", "utf-8") - - while True: - if self._canceled: - _log.debug('Testing cancelled') - tests_run_file.close() - return - - if len(self._filename_list) is 0: - if self._current_group is not None: - self._test_group_timing_stats[self._current_group] = \ - (self._num_tests_in_current_group, - time.time() - self._current_group_start_time) - - try: - self._current_group, self._filename_list = \ - self._filename_list_queue.get_nowait() - except Queue.Empty: - self._stop_servers_with_lock() - self._kill_dump_render_tree() - tests_run_file.close() - return - - if self._current_group == "tests_to_http_lock": - self._start_servers_with_lock() - elif self._have_http_lock: - self._stop_servers_with_lock() - - self._num_tests_in_current_group = len(self._filename_list) - self._current_group_start_time = time.time() - - test_input = self._filename_list.pop() - - # We have a url, run tests. - batch_count += 1 - self._num_tests += 1 - if self._options.run_singly: - result = self._run_test_in_another_thread(test_input) - else: - result = self._run_test_in_this_thread(test_input) - - filename = test_input.filename - tests_run_file.write(filename + "\n") - if result.failures: - # Check and kill DumpRenderTree if we need to. - if len([1 for f in result.failures - if f.should_kill_dump_render_tree()]): - self._kill_dump_render_tree() - # Reset the batch count since the shell just bounced. - batch_count = 0 - # Print the error message(s). - error_str = '\n'.join([' ' + f.message() for - f in result.failures]) - _log.debug("%s %s failed:\n%s" % (self.getName(), - self._port.relative_test_filename(filename), - error_str)) - else: - _log.debug("%s %s passed" % (self.getName(), - self._port.relative_test_filename(filename))) - self._result_queue.put(result.dumps()) - - if batch_size > 0 and batch_count >= batch_size: - # Bounce the shell and reset count. - self._kill_dump_render_tree() - batch_count = 0 - - if test_runner: - test_runner.update_summary(result_summary) - - def _run_test_in_another_thread(self, test_input): - """Run a test in a separate thread, enforcing a hard time limit. - - Since we can only detect the termination of a thread, not any internal - state or progress, we can only run per-test timeouts when running test - files singly. - - Args: - test_input: Object containing the test filename and timeout - - Returns: - A TestResult - """ - worker = SingleTestThread(self._port, - self._options, - self._worker_number, - self._name, - test_input, - self._test_types, - self._test_args) - - worker.start() - - thread_timeout = _milliseconds_to_seconds( - _pad_timeout(int(test_input.timeout))) - thread._next_timeout = time.time() + thread_timeout - worker.join(thread_timeout) - if worker.isAlive(): - # If join() returned with the thread still running, the - # DumpRenderTree is completely hung and there's nothing - # more we can do with it. We have to kill all the - # DumpRenderTrees to free it up. If we're running more than - # one DumpRenderTree thread, we'll end up killing the other - # DumpRenderTrees too, introducing spurious crashes. We accept - # that tradeoff in order to avoid losing the rest of this - # thread's results. - _log.error('Test thread hung: killing all DumpRenderTrees') - if worker._driver: - worker._driver.stop() - - try: - result = worker.get_test_result() - except AttributeError, e: - # This gets raised if the worker thread has already exited. - failures = [] - _log.error('Cannot get results of test: %s' % - test_input.filename) - result = test_results.TestResult(test_input.filename, failures=[], - test_run_time=0, total_time_for_all_diffs=0, time_for_diffs={}) - - return result - - def _run_test_in_this_thread(self, test_input): - """Run a single test file using a shared DumpRenderTree process. - - Args: - test_input: Object containing the test filename, uri and timeout - - Returns: a TestResult object. - """ - self._ensure_dump_render_tree_is_running() - thread_timeout = _milliseconds_to_seconds( - _pad_timeout(int(test_input.timeout))) - self._next_timeout = time.time() + thread_timeout - test_result = _run_single_test(self._port, self._options, test_input, - self._test_types, self._test_args, - self._driver, self._name) - self._test_results.append(test_result) - return test_result - - def _ensure_dump_render_tree_is_running(self): - """Start the shared DumpRenderTree, if it's not running. - - This is not for use when running tests singly, since those each start - a separate DumpRenderTree in their own thread. - - """ - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - if not self._driver or self._driver.poll() is not None: - self._driver = self._port.create_driver(self._worker_number) - self._driver.start() - - def _start_servers_with_lock(self): - """Acquire http lock and start the servers.""" - self._http_lock_wait_begin = time.time() - _log.debug('Acquire http lock ...') - self._port.acquire_http_lock() - _log.debug('Starting HTTP server ...') - self._port.start_http_server() - _log.debug('Starting WebSocket server ...') - self._port.start_websocket_server() - self._http_lock_wait_end = time.time() - self._have_http_lock = True - - def _stop_servers_with_lock(self): - """Stop the servers and release http lock.""" - if self._have_http_lock: - _log.debug('Stopping HTTP server ...') - self._port.stop_http_server() - _log.debug('Stopping WebSocket server ...') - self._port.stop_websocket_server() - _log.debug('Release http lock ...') - self._port.release_http_lock() - self._have_http_lock = False - - def _kill_dump_render_tree(self): - """Kill the DumpRenderTree process if it's running.""" - if self._driver: - self._driver.stop() - self._driver = None diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py deleted file mode 100644 index b054c5b..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py +++ /dev/null @@ -1,212 +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 os - -from webkitpy.layout_tests.layout_package import json_results_generator -from webkitpy.layout_tests.layout_package import test_expectations -from webkitpy.layout_tests.layout_package import test_failures -import webkitpy.thirdparty.simplejson as simplejson - - -class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGeneratorBase): - """A JSON results generator for layout tests.""" - - LAYOUT_TESTS_PATH = "LayoutTests" - - # Additional JSON fields. - WONTFIX = "wontfixCounts" - - # Note that we omit test_expectations.FAIL from this list because - # it should never show up (it's a legacy input expectation, never - # an output expectation). - FAILURE_TO_CHAR = {test_expectations.CRASH: "C", - test_expectations.TIMEOUT: "T", - test_expectations.IMAGE: "I", - test_expectations.TEXT: "F", - test_expectations.MISSING: "O", - test_expectations.IMAGE_PLUS_TEXT: "Z"} - - def __init__(self, port, builder_name, build_name, build_number, - results_file_base_path, builder_base_url, - test_timings, expectations, result_summary, all_tests, - generate_incremental_results=False, test_results_server=None, - test_type="", master_name=""): - """Modifies the results.json file. Grabs it off the archive directory - if it is not found locally. - - Args: - result_summary: ResultsSummary object storing the summary of the test - results. - """ - super(JSONLayoutResultsGenerator, self).__init__( - builder_name, build_name, build_number, results_file_base_path, - builder_base_url, {}, port.test_repository_paths(), - generate_incremental_results, test_results_server, - test_type, master_name) - - self._port = port - self._expectations = expectations - - # We want relative paths to LayoutTest root for JSON output. - path_to_name = self._get_path_relative_to_layout_test_root - self._result_summary = result_summary - self._failures = dict( - (path_to_name(test), test_failures.determine_result_type(failures)) - for (test, failures) in result_summary.failures.iteritems()) - self._all_tests = [path_to_name(test) for test in all_tests] - self._test_timings = dict( - (path_to_name(test_tuple.filename), test_tuple.test_run_time) - for test_tuple in test_timings) - - self.generate_json_output() - - def _get_path_relative_to_layout_test_root(self, test): - """Returns the path of the test relative to the layout test root. - For example, for: - src/third_party/WebKit/LayoutTests/fast/forms/foo.html - We would return - fast/forms/foo.html - """ - index = test.find(self.LAYOUT_TESTS_PATH) - if index is not -1: - index += len(self.LAYOUT_TESTS_PATH) - - if index is -1: - # Already a relative path. - relativePath = test - else: - relativePath = test[index + 1:] - - # Make sure all paths are unix-style. - return relativePath.replace('\\', '/') - - # override - def _get_test_timing(self, test_name): - if test_name in self._test_timings: - # Floor for now to get time in seconds. - return int(self._test_timings[test_name]) - return 0 - - # override - def _get_failed_test_names(self): - return set(self._failures.keys()) - - # override - def _get_modifier_char(self, test_name): - if test_name not in self._all_tests: - return self.NO_DATA_RESULT - - if test_name in self._failures: - return self.FAILURE_TO_CHAR[self._failures[test_name]] - - return self.PASS_RESULT - - # override - def _get_result_char(self, test_name): - return self._get_modifier_char(test_name) - - # override - def _convert_json_to_current_version(self, results_json): - archive_version = None - if self.VERSION_KEY in results_json: - archive_version = results_json[self.VERSION_KEY] - - super(JSONLayoutResultsGenerator, - self)._convert_json_to_current_version(results_json) - - # version 2->3 - if archive_version == 2: - for results_for_builder in results_json.itervalues(): - try: - test_results = results_for_builder[self.TESTS] - except: - continue - - for test in test_results: - # Make sure all paths are relative - test_path = self._get_path_relative_to_layout_test_root(test) - if test_path != test: - test_results[test_path] = test_results[test] - del test_results[test] - - # override - def _insert_failure_summaries(self, results_for_builder): - summary = self._result_summary - - self._insert_item_into_raw_list(results_for_builder, - len((set(summary.failures.keys()) | - summary.tests_by_expectation[test_expectations.SKIP]) & - summary.tests_by_timeline[test_expectations.NOW]), - self.FIXABLE_COUNT) - self._insert_item_into_raw_list(results_for_builder, - self._get_failure_summary_entry(test_expectations.NOW), - self.FIXABLE) - self._insert_item_into_raw_list(results_for_builder, - len(self._expectations.get_tests_with_timeline( - test_expectations.NOW)), self.ALL_FIXABLE_COUNT) - self._insert_item_into_raw_list(results_for_builder, - self._get_failure_summary_entry(test_expectations.WONTFIX), - self.WONTFIX) - - # override - def _normalize_results_json(self, test, test_name, tests): - super(JSONLayoutResultsGenerator, self)._normalize_results_json( - test, test_name, tests) - - # Remove tests that don't exist anymore. - full_path = os.path.join(self._port.layout_tests_dir(), test_name) - full_path = os.path.normpath(full_path) - if not os.path.exists(full_path): - del tests[test_name] - - def _get_failure_summary_entry(self, timeline): - """Creates a summary object to insert into the JSON. - - Args: - summary ResultSummary object with test results - timeline current test_expectations timeline to build entry for - (e.g., test_expectations.NOW, etc.) - """ - entry = {} - summary = self._result_summary - timeline_tests = summary.tests_by_timeline[timeline] - entry[self.SKIP_RESULT] = len( - summary.tests_by_expectation[test_expectations.SKIP] & - timeline_tests) - entry[self.PASS_RESULT] = len( - summary.tests_by_expectation[test_expectations.PASS] & - timeline_tests) - for failure_type in summary.tests_by_expectation.keys(): - if failure_type not in self.FAILURE_TO_CHAR: - continue - count = len(summary.tests_by_expectation[failure_type] & - timeline_tests) - entry[self.FAILURE_TO_CHAR[failure_type]] = count - return entry diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py deleted file mode 100644 index 331e330..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py +++ /dev/null @@ -1,661 +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 logging -import os -import subprocess -import sys -import time -import urllib2 -import xml.dom.minidom - -from webkitpy.layout_tests.layout_package import test_results_uploader - -import webkitpy.thirdparty.simplejson as simplejson - -# A JSON results generator for generic tests. -# FIXME: move this code out of the layout_package directory. - -_log = logging.getLogger("webkitpy.layout_tests.layout_package.json_results_generator") - -class TestResult(object): - """A simple class that represents a single test result.""" - - # Test modifier constants. - (NONE, FAILS, FLAKY, DISABLED) = range(4) - - def __init__(self, name, failed=False, elapsed_time=0): - self.name = name - self.failed = failed - self.time = elapsed_time - - test_name = name - try: - test_name = name.split('.')[1] - except IndexError: - _log.warn("Invalid test name: %s.", name) - pass - - if test_name.startswith('FAILS_'): - self.modifier = self.FAILS - elif test_name.startswith('FLAKY_'): - self.modifier = self.FLAKY - elif test_name.startswith('DISABLED_'): - self.modifier = self.DISABLED - else: - self.modifier = self.NONE - - def fixable(self): - return self.failed or self.modifier == self.DISABLED - - -class JSONResultsGeneratorBase(object): - """A JSON results generator for generic tests.""" - - MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG = 750 - # Min time (seconds) that will be added to the JSON. - MIN_TIME = 1 - JSON_PREFIX = "ADD_RESULTS(" - JSON_SUFFIX = ");" - - # Note that in non-chromium tests those chars are used to indicate - # test modifiers (FAILS, FLAKY, etc) but not actual test results. - PASS_RESULT = "P" - SKIP_RESULT = "X" - FAIL_RESULT = "F" - FLAKY_RESULT = "L" - NO_DATA_RESULT = "N" - - MODIFIER_TO_CHAR = {TestResult.NONE: PASS_RESULT, - TestResult.DISABLED: SKIP_RESULT, - TestResult.FAILS: FAIL_RESULT, - TestResult.FLAKY: FLAKY_RESULT} - - VERSION = 3 - VERSION_KEY = "version" - RESULTS = "results" - TIMES = "times" - BUILD_NUMBERS = "buildNumbers" - TIME = "secondsSinceEpoch" - TESTS = "tests" - - FIXABLE_COUNT = "fixableCount" - FIXABLE = "fixableCounts" - ALL_FIXABLE_COUNT = "allFixableCount" - - RESULTS_FILENAME = "results.json" - INCREMENTAL_RESULTS_FILENAME = "incremental_results.json" - - URL_FOR_TEST_LIST_JSON = \ - "http://%s/testfile?builder=%s&name=%s&testlistjson=1&testtype=%s" - - def __init__(self, builder_name, build_name, build_number, - results_file_base_path, builder_base_url, - test_results_map, svn_repositories=None, - generate_incremental_results=False, - test_results_server=None, - test_type="", - master_name=""): - """Modifies the results.json file. Grabs it off the archive directory - if it is not found locally. - - Args - builder_name: the builder name (e.g. Webkit). - build_name: the build name (e.g. webkit-rel). - build_number: the build number. - results_file_base_path: Absolute path to the directory containing the - results json file. - builder_base_url: the URL where we have the archived test results. - If this is None no archived results will be retrieved. - test_results_map: A dictionary that maps test_name to TestResult. - svn_repositories: A (json_field_name, svn_path) pair for SVN - repositories that tests rely on. The SVN revision will be - included in the JSON with the given json_field_name. - generate_incremental_results: If true, generate incremental json file - from current run results. - test_results_server: server that hosts test results json. - test_type: test type string (e.g. 'layout-tests'). - master_name: the name of the buildbot master. - """ - self._builder_name = builder_name - self._build_name = build_name - self._build_number = build_number - self._builder_base_url = builder_base_url - self._results_directory = results_file_base_path - self._results_file_path = os.path.join(results_file_base_path, - self.RESULTS_FILENAME) - self._incremental_results_file_path = os.path.join( - results_file_base_path, self.INCREMENTAL_RESULTS_FILENAME) - - self._test_results_map = test_results_map - self._test_results = test_results_map.values() - self._generate_incremental_results = generate_incremental_results - - self._svn_repositories = svn_repositories - if not self._svn_repositories: - self._svn_repositories = {} - - self._test_results_server = test_results_server - self._test_type = test_type - self._master_name = master_name - - self._json = None - self._archived_results = None - - def generate_json_output(self): - """Generates the JSON output file.""" - - # Generate the JSON output file that has full results. - # FIXME: stop writing out the full results file once all bots use - # incremental results. - if not self._json: - self._json = self.get_json() - if self._json: - self._generate_json_file(self._json, self._results_file_path) - - # Generate the JSON output file that only has incremental results. - if self._generate_incremental_results: - json = self.get_json(incremental=True) - if json: - self._generate_json_file( - json, self._incremental_results_file_path) - - def get_json(self, incremental=False): - """Gets the results for the results.json file.""" - results_json = {} - if not incremental: - if self._json: - return self._json - - if self._archived_results: - results_json = self._archived_results - - if not results_json: - results_json, error = self._get_archived_json_results(incremental) - if error: - # If there was an error don't write a results.json - # file at all as it would lose all the information on the - # bot. - _log.error("Archive directory is inaccessible. Not " - "modifying or clobbering the results.json " - "file: " + str(error)) - return None - - builder_name = self._builder_name - if results_json and builder_name not in results_json: - _log.debug("Builder name (%s) is not in the results.json file." - % builder_name) - - self._convert_json_to_current_version(results_json) - - if builder_name not in results_json: - results_json[builder_name] = ( - self._create_results_for_builder_json()) - - results_for_builder = results_json[builder_name] - - self._insert_generic_metadata(results_for_builder) - - self._insert_failure_summaries(results_for_builder) - - # Update the all failing tests with result type and time. - tests = results_for_builder[self.TESTS] - all_failing_tests = self._get_failed_test_names() - all_failing_tests.update(tests.iterkeys()) - for test in all_failing_tests: - self._insert_test_time_and_result(test, tests, incremental) - - return results_json - - def set_archived_results(self, archived_results): - self._archived_results = archived_results - - def upload_json_files(self, json_files): - """Uploads the given json_files to the test_results_server (if the - test_results_server is given).""" - if not self._test_results_server: - return - - if not self._master_name: - _log.error("--test-results-server was set, but --master-name was not. Not uploading JSON files.") - return - - _log.info("Uploading JSON files for builder: %s", self._builder_name) - attrs = [("builder", self._builder_name), - ("testtype", self._test_type), - ("master", self._master_name)] - - files = [(file, os.path.join(self._results_directory, file)) - for file in json_files] - - uploader = test_results_uploader.TestResultsUploader( - self._test_results_server) - try: - # Set uploading timeout in case appengine server is having problem. - # 120 seconds are more than enough to upload test results. - uploader.upload(attrs, files, 120) - except Exception, err: - _log.error("Upload failed: %s" % err) - return - - _log.info("JSON files uploaded.") - - def _generate_json_file(self, json, file_path): - # Specify separators in order to get compact encoding. - json_data = simplejson.dumps(json, separators=(',', ':')) - json_string = self.JSON_PREFIX + json_data + self.JSON_SUFFIX - - results_file = codecs.open(file_path, "w", "utf-8") - results_file.write(json_string) - results_file.close() - - def _get_test_timing(self, test_name): - """Returns test timing data (elapsed time) in second - for the given test_name.""" - if test_name in self._test_results_map: - # Floor for now to get time in seconds. - return int(self._test_results_map[test_name].time) - return 0 - - def _get_failed_test_names(self): - """Returns a set of failed test names.""" - return set([r.name for r in self._test_results if r.failed]) - - def _get_modifier_char(self, test_name): - """Returns a single char (e.g. SKIP_RESULT, FAIL_RESULT, - PASS_RESULT, NO_DATA_RESULT, etc) that indicates the test modifier - for the given test_name. - """ - if test_name not in self._test_results_map: - return JSONResultsGenerator.NO_DATA_RESULT - - test_result = self._test_results_map[test_name] - if test_result.modifier in self.MODIFIER_TO_CHAR.keys(): - return self.MODIFIER_TO_CHAR[test_result.modifier] - - return JSONResultsGenerator.PASS_RESULT - - def _get_result_char(self, test_name): - """Returns a single char (e.g. SKIP_RESULT, FAIL_RESULT, - PASS_RESULT, NO_DATA_RESULT, etc) that indicates the test result - for the given test_name. - """ - if test_name not in self._test_results_map: - return JSONResultsGenerator.NO_DATA_RESULT - - test_result = self._test_results_map[test_name] - if test_result.modifier == TestResult.DISABLED: - return JSONResultsGenerator.SKIP_RESULT - - if test_result.failed: - return JSONResultsGenerator.FAIL_RESULT - - return JSONResultsGenerator.PASS_RESULT - - # FIXME: Callers should use scm.py instead. - # FIXME: Identify and fix the run-time errors that were observed on Windows - # chromium buildbot when we had updated this code to use scm.py once before. - def _get_svn_revision(self, in_directory): - """Returns the svn revision for the given directory. - - Args: - in_directory: The directory where svn is to be run. - """ - if os.path.exists(os.path.join(in_directory, '.svn')): - # Note: Not thread safe: http://bugs.python.org/issue2320 - output = subprocess.Popen(["svn", "info", "--xml"], - cwd=in_directory, - shell=(sys.platform == 'win32'), - stdout=subprocess.PIPE).communicate()[0] - try: - dom = xml.dom.minidom.parseString(output) - return dom.getElementsByTagName('entry')[0].getAttribute( - 'revision') - except xml.parsers.expat.ExpatError: - return "" - return "" - - def _get_archived_json_results(self, for_incremental=False): - """Reads old results JSON file if it exists. - Returns (archived_results, error) tuple where error is None if results - were successfully read. - - if for_incremental is True, download JSON file that only contains test - name list from test-results server. This is for generating incremental - JSON so the file generated has info for tests that failed before but - pass or are skipped from current run. - """ - results_json = {} - old_results = None - error = None - - if os.path.exists(self._results_file_path) and not for_incremental: - with codecs.open(self._results_file_path, "r", "utf-8") as file: - old_results = file.read() - elif self._builder_base_url or for_incremental: - if for_incremental: - if not self._test_results_server: - # starting from fresh if no test results server specified. - return {}, None - - results_file_url = (self.URL_FOR_TEST_LIST_JSON % - (urllib2.quote(self._test_results_server), - urllib2.quote(self._builder_name), - self.RESULTS_FILENAME, - urllib2.quote(self._test_type))) - else: - # Check if we have the archived JSON file on the buildbot - # server. - results_file_url = (self._builder_base_url + - self._build_name + "/" + self.RESULTS_FILENAME) - _log.error("Local results.json file does not exist. Grabbing " - "it off the archive at " + results_file_url) - - try: - results_file = urllib2.urlopen(results_file_url) - info = results_file.info() - old_results = results_file.read() - except urllib2.HTTPError, http_error: - # A non-4xx status code means the bot is hosed for some reason - # and we can't grab the results.json file off of it. - if (http_error.code < 400 and http_error.code >= 500): - error = http_error - except urllib2.URLError, url_error: - error = url_error - - if old_results: - # Strip the prefix and suffix so we can get the actual JSON object. - old_results = old_results[len(self.JSON_PREFIX): - len(old_results) - len(self.JSON_SUFFIX)] - - try: - results_json = simplejson.loads(old_results) - except: - _log.debug("results.json was not valid JSON. Clobbering.") - # The JSON file is not valid JSON. Just clobber the results. - results_json = {} - else: - _log.debug('Old JSON results do not exist. Starting fresh.') - results_json = {} - - return results_json, error - - def _insert_failure_summaries(self, results_for_builder): - """Inserts aggregate pass/failure statistics into the JSON. - This method reads self._test_results and generates - FIXABLE, FIXABLE_COUNT and ALL_FIXABLE_COUNT entries. - - Args: - results_for_builder: Dictionary containing the test results for a - single builder. - """ - # Insert the number of tests that failed or skipped. - fixable_count = len([r for r in self._test_results if r.fixable()]) - self._insert_item_into_raw_list(results_for_builder, - fixable_count, self.FIXABLE_COUNT) - - # Create a test modifiers (FAILS, FLAKY etc) summary dictionary. - entry = {} - for test_name in self._test_results_map.iterkeys(): - result_char = self._get_modifier_char(test_name) - entry[result_char] = entry.get(result_char, 0) + 1 - - # Insert the pass/skip/failure summary dictionary. - self._insert_item_into_raw_list(results_for_builder, entry, - self.FIXABLE) - - # Insert the number of all the tests that are supposed to pass. - all_test_count = len(self._test_results) - self._insert_item_into_raw_list(results_for_builder, - all_test_count, self.ALL_FIXABLE_COUNT) - - def _insert_item_into_raw_list(self, results_for_builder, item, key): - """Inserts the item into the list with the given key in the results for - this builder. Creates the list if no such list exists. - - Args: - results_for_builder: Dictionary containing the test results for a - single builder. - item: Number or string to insert into the list. - key: Key in results_for_builder for the list to insert into. - """ - if key in results_for_builder: - raw_list = results_for_builder[key] - else: - raw_list = [] - - raw_list.insert(0, item) - raw_list = raw_list[:self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG] - results_for_builder[key] = raw_list - - def _insert_item_run_length_encoded(self, item, encoded_results): - """Inserts the item into the run-length encoded results. - - Args: - item: String or number to insert. - encoded_results: run-length encoded results. An array of arrays, e.g. - [[3,'A'],[1,'Q']] encodes AAAQ. - """ - if len(encoded_results) and item == encoded_results[0][1]: - num_results = encoded_results[0][0] - if num_results <= self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG: - encoded_results[0][0] = num_results + 1 - else: - # Use a list instead of a class for the run-length encoding since - # we want the serialized form to be concise. - encoded_results.insert(0, [1, item]) - - def _insert_generic_metadata(self, results_for_builder): - """ Inserts generic metadata (such as version number, current time etc) - into the JSON. - - Args: - results_for_builder: Dictionary containing the test results for - a single builder. - """ - self._insert_item_into_raw_list(results_for_builder, - self._build_number, self.BUILD_NUMBERS) - - # Include SVN revisions for the given repositories. - for (name, path) in self._svn_repositories: - self._insert_item_into_raw_list(results_for_builder, - self._get_svn_revision(path), - name + 'Revision') - - self._insert_item_into_raw_list(results_for_builder, - int(time.time()), - self.TIME) - - def _insert_test_time_and_result(self, test_name, tests, incremental=False): - """ Insert a test item with its results to the given tests dictionary. - - Args: - tests: Dictionary containing test result entries. - """ - - result = self._get_result_char(test_name) - time = self._get_test_timing(test_name) - - if test_name not in tests: - tests[test_name] = self._create_results_and_times_json() - - thisTest = tests[test_name] - if self.RESULTS in thisTest: - self._insert_item_run_length_encoded(result, thisTest[self.RESULTS]) - else: - thisTest[self.RESULTS] = [[1, result]] - - if self.TIMES in thisTest: - self._insert_item_run_length_encoded(time, thisTest[self.TIMES]) - else: - thisTest[self.TIMES] = [[1, time]] - - # Don't normalize the incremental results json because we need results - # for tests that pass or have no data from current run. - if not incremental: - self._normalize_results_json(thisTest, test_name, tests) - - def _convert_json_to_current_version(self, results_json): - """If the JSON does not match the current version, converts it to the - current version and adds in the new version number. - """ - if (self.VERSION_KEY in results_json and - results_json[self.VERSION_KEY] == self.VERSION): - return - - results_json[self.VERSION_KEY] = self.VERSION - - def _create_results_and_times_json(self): - results_and_times = {} - results_and_times[self.RESULTS] = [] - results_and_times[self.TIMES] = [] - return results_and_times - - def _create_results_for_builder_json(self): - results_for_builder = {} - results_for_builder[self.TESTS] = {} - return results_for_builder - - def _remove_items_over_max_number_of_builds(self, encoded_list): - """Removes items from the run-length encoded list after the final - item that exceeds the max number of builds to track. - - Args: - encoded_results: run-length encoded results. An array of arrays, e.g. - [[3,'A'],[1,'Q']] encodes AAAQ. - """ - num_builds = 0 - index = 0 - for result in encoded_list: - num_builds = num_builds + result[0] - index = index + 1 - if num_builds > self.MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG: - return encoded_list[:index] - return encoded_list - - def _normalize_results_json(self, test, test_name, tests): - """ Prune tests where all runs pass or tests that no longer exist and - truncate all results to maxNumberOfBuilds. - - Args: - test: ResultsAndTimes object for this test. - test_name: Name of the test. - tests: The JSON object with all the test results for this builder. - """ - test[self.RESULTS] = self._remove_items_over_max_number_of_builds( - test[self.RESULTS]) - test[self.TIMES] = self._remove_items_over_max_number_of_builds( - test[self.TIMES]) - - is_all_pass = self._is_results_all_of_type(test[self.RESULTS], - self.PASS_RESULT) - is_all_no_data = self._is_results_all_of_type(test[self.RESULTS], - self.NO_DATA_RESULT) - max_time = max([time[1] for time in test[self.TIMES]]) - - # Remove all passes/no-data from the results to reduce noise and - # filesize. If a test passes every run, but takes > MIN_TIME to run, - # don't throw away the data. - if is_all_no_data or (is_all_pass and max_time <= self.MIN_TIME): - del tests[test_name] - - def _is_results_all_of_type(self, results, type): - """Returns whether all the results are of the given type - (e.g. all passes).""" - return len(results) == 1 and results[0][1] == type - - -# A wrapper class for JSONResultsGeneratorBase. -# Note: There's a script outside the WebKit codebase calling this script. -# FIXME: Please keep the interface until the other script is cleaned up. -# (http://src.chromium.org/viewvc/chrome/trunk/src/webkit/tools/layout_tests/webkitpy/layout_tests/test_output_xml_to_json.py?view=markup) -class JSONResultsGenerator(JSONResultsGeneratorBase): - # The flag is for backward compatibility. - output_json_in_init = True - - def __init__(self, port, builder_name, build_name, build_number, - results_file_base_path, builder_base_url, - test_timings, failures, passed_tests, skipped_tests, all_tests, - test_results_server=None, test_type=None, master_name=None): - """Generates a JSON results file. - - Args - builder_name: the builder name (e.g. Webkit). - build_name: the build name (e.g. webkit-rel). - build_number: the build number. - results_file_base_path: Absolute path to the directory containing the - results json file. - builder_base_url: the URL where we have the archived test results. - test_timings: Map of test name to a test_run-time. - failures: Map of test name to a failure type (of test_expectations). - passed_tests: A set containing all the passed tests. - skipped_tests: A set containing all the skipped tests. - all_tests: List of all the tests that were run. This should not - include skipped tests. - test_results_server: server that hosts test results json. - test_type: the test type. - master_name: the name of the buildbot master. - """ - - self._test_type = test_type - self._results_directory = results_file_base_path - - # Create a map of (name, TestResult). - test_results_map = dict() - get = test_results_map.get - for (test, time) in test_timings.iteritems(): - test_results_map[test] = TestResult(test, elapsed_time=time) - for test in failures.iterkeys(): - test_results_map[test] = test_result = get(test, TestResult(test)) - test_result.failed = True - for test in skipped_tests: - test_results_map[test] = test_result = get(test, TestResult(test)) - for test in passed_tests: - test_results_map[test] = test_result = get(test, TestResult(test)) - test_result.failed = False - for test in all_tests: - if test not in test_results_map: - test_results_map[test] = TestResult(test) - - # Generate the JSON with incremental flag enabled. - # (This should also output the full result for now.) - super(JSONResultsGenerator, self).__init__( - builder_name, build_name, build_number, - results_file_base_path, builder_base_url, test_results_map, - svn_repositories=port.test_repository_paths(), - generate_incremental_results=True, - test_results_server=test_results_server, - test_type=test_type, - master_name=master_name) - - if self.__class__.output_json_in_init: - self.generate_json_output() - self.upload_json_files([self.INCREMENTAL_RESULTS_FILENAME]) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py deleted file mode 100644 index d6275ee..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator_unittest.py +++ /dev/null @@ -1,205 +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. - -"""Unit tests for json_results_generator.py.""" - -import unittest -import optparse -import random -import shutil -import tempfile - -from webkitpy.layout_tests.layout_package import json_results_generator -from webkitpy.layout_tests.layout_package import test_expectations -from webkitpy.layout_tests import port - - -class JSONGeneratorTest(unittest.TestCase): - def setUp(self): - json_results_generator.JSONResultsGenerator.output_json_in_init = False - self.builder_name = 'DUMMY_BUILDER_NAME' - self.build_name = 'DUMMY_BUILD_NAME' - self.build_number = 'DUMMY_BUILDER_NUMBER' - self._json = None - self._num_runs = 0 - self._tests_set = set([]) - self._test_timings = {} - self._failed_tests = set([]) - - self._PASS_tests = set([]) - self._DISABLED_tests = set([]) - self._FLAKY_tests = set([]) - self._FAILS_tests = set([]) - - def _test_json_generation(self, passed_tests_list, failed_tests_list): - tests_set = set(passed_tests_list) | set(failed_tests_list) - - DISABLED_tests = set([t for t in tests_set - if t.startswith('DISABLED_')]) - FLAKY_tests = set([t for t in tests_set - if t.startswith('FLAKY_')]) - FAILS_tests = set([t for t in tests_set - if t.startswith('FAILS_')]) - PASS_tests = tests_set - (DISABLED_tests | FLAKY_tests | FAILS_tests) - - passed_tests = set(passed_tests_list) - DISABLED_tests - failed_tests = set(failed_tests_list) - - test_timings = {} - i = 0 - for test in tests_set: - test_timings[test] = float(self._num_runs * 100 + i) - i += 1 - - # For backward compatibility. - reason = test_expectations.TEXT - failed_tests_dict = dict([(name, reason) for name in failed_tests]) - - port_obj = port.get(None) - generator = json_results_generator.JSONResultsGenerator(port_obj, - self.builder_name, self.build_name, self.build_number, - '', - None, # don't fetch past json results archive - test_timings, - failed_tests_dict, - passed_tests, - (), - tests_set) - - # Test incremental json results - incremental_json = generator.get_json(incremental=True) - self._verify_json_results( - tests_set, - test_timings, - failed_tests, - PASS_tests, - DISABLED_tests, - FLAKY_tests, - incremental_json, - 1) - - # Test aggregated json results - generator.set_archived_results(self._json) - json = generator.get_json(incremental=False) - self._json = json - self._num_runs += 1 - self._tests_set |= tests_set - self._test_timings.update(test_timings) - self._failed_tests.update(failed_tests) - self._PASS_tests |= PASS_tests - self._DISABLED_tests |= DISABLED_tests - self._FLAKY_tests |= FLAKY_tests - self._verify_json_results( - self._tests_set, - self._test_timings, - self._failed_tests, - self._PASS_tests, - self._DISABLED_tests, - self._FLAKY_tests, - self._json, - self._num_runs) - - def _verify_json_results(self, tests_set, test_timings, failed_tests, - PASS_tests, DISABLED_tests, FLAKY_tests, - json, num_runs): - # Aliasing to a short name for better access to its constants. - JRG = json_results_generator.JSONResultsGenerator - - self.assertTrue(JRG.VERSION_KEY in json) - self.assertTrue(self.builder_name in json) - - buildinfo = json[self.builder_name] - self.assertTrue(JRG.FIXABLE in buildinfo) - self.assertTrue(JRG.TESTS in buildinfo) - self.assertEqual(len(buildinfo[JRG.BUILD_NUMBERS]), num_runs) - self.assertEqual(buildinfo[JRG.BUILD_NUMBERS][0], self.build_number) - - if tests_set or DISABLED_tests: - fixable = {} - for fixable_items in buildinfo[JRG.FIXABLE]: - for (type, count) in fixable_items.iteritems(): - if type in fixable: - fixable[type] = fixable[type] + count - else: - fixable[type] = count - - if PASS_tests: - self.assertEqual(fixable[JRG.PASS_RESULT], len(PASS_tests)) - else: - self.assertTrue(JRG.PASS_RESULT not in fixable or - fixable[JRG.PASS_RESULT] == 0) - if DISABLED_tests: - self.assertEqual(fixable[JRG.SKIP_RESULT], len(DISABLED_tests)) - else: - self.assertTrue(JRG.SKIP_RESULT not in fixable or - fixable[JRG.SKIP_RESULT] == 0) - if FLAKY_tests: - self.assertEqual(fixable[JRG.FLAKY_RESULT], len(FLAKY_tests)) - else: - self.assertTrue(JRG.FLAKY_RESULT not in fixable or - fixable[JRG.FLAKY_RESULT] == 0) - - if failed_tests: - tests = buildinfo[JRG.TESTS] - for test_name in failed_tests: - self.assertTrue(test_name in tests) - test = tests[test_name] - - failed = 0 - for result in test[JRG.RESULTS]: - if result[1] == JRG.FAIL_RESULT: - failed = result[0] - - self.assertEqual(1, failed) - - timing_count = 0 - for timings in test[JRG.TIMES]: - if timings[1] == test_timings[test_name]: - timing_count = timings[0] - self.assertEqual(1, timing_count) - - fixable_count = len(DISABLED_tests | failed_tests) - if DISABLED_tests or failed_tests: - self.assertEqual(sum(buildinfo[JRG.FIXABLE_COUNT]), fixable_count) - - def test_json_generation(self): - self._test_json_generation([], []) - self._test_json_generation(['A1', 'B1'], []) - self._test_json_generation([], ['FAILS_A2', 'FAILS_B2']) - self._test_json_generation(['DISABLED_A3', 'DISABLED_B3'], []) - self._test_json_generation(['A4'], ['B4', 'FAILS_C4']) - self._test_json_generation(['DISABLED_C5', 'DISABLED_D5'], ['A5', 'B5']) - self._test_json_generation( - ['A6', 'B6', 'FAILS_C6', 'DISABLED_E6', 'DISABLED_F6'], - ['FAILS_D6']) - self._test_json_generation( - ['A7', 'FLAKY_B7', 'DISABLED_C7'], - ['FAILS_D7', 'FLAKY_D8']) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py deleted file mode 100644 index e520a9c..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/message_broker.py +++ /dev/null @@ -1,197 +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. - -"""Module for handling messages, threads, processes, and concurrency for run-webkit-tests. - -Testing is accomplished by having a manager (TestRunner) gather all of the -tests to be run, and sending messages to a pool of workers (TestShellThreads) -to run each test. Each worker communicates with one driver (usually -DumpRenderTree) to run one test at a time and then compare the output against -what we expected to get. - -This modules provides a message broker that connects the manager to the -workers: it provides a messaging abstraction and message loops, and -handles launching threads and/or processes depending on the -requested configuration. -""" - -import logging -import sys -import time -import traceback - -import dump_render_tree_thread - -_log = logging.getLogger(__name__) - - -def get(port, options): - """Return an instance of a WorkerMessageBroker.""" - worker_model = options.worker_model - if worker_model == 'inline': - return InlineBroker(port, options) - if worker_model == 'threads': - return MultiThreadedBroker(port, options) - raise ValueError('unsupported value for --worker-model: %s' % worker_model) - - -class _WorkerState(object): - def __init__(self, name): - self.name = name - self.thread = None - - -class WorkerMessageBroker(object): - def __init__(self, port, options): - self._port = port - self._options = options - self._num_workers = int(self._options.child_processes) - - # This maps worker names to their _WorkerState values. - self._workers = {} - - def _threads(self): - return tuple([w.thread for w in self._workers.values()]) - - def start_workers(self, test_runner): - """Starts up the pool of workers for running the tests. - - Args: - test_runner: a handle to the manager/TestRunner object - """ - self._test_runner = test_runner - for worker_number in xrange(self._num_workers): - worker = _WorkerState('worker-%d' % worker_number) - worker.thread = self._start_worker(worker_number, worker.name) - self._workers[worker.name] = worker - return self._threads() - - def _start_worker(self, worker_number, worker_name): - raise NotImplementedError - - def run_message_loop(self): - """Loop processing messages until done.""" - raise NotImplementedError - - def cancel_workers(self): - """Cancel/interrupt any workers that are still alive.""" - pass - - def cleanup(self): - """Perform any necessary cleanup on shutdown.""" - pass - - -class InlineBroker(WorkerMessageBroker): - def _start_worker(self, worker_number, worker_name): - # FIXME: Replace with something that isn't a thread. - thread = dump_render_tree_thread.TestShellThread(self._port, - self._options, worker_number, worker_name, - self._test_runner._current_filename_queue, - self._test_runner._result_queue) - # Note: Don't start() the thread! If we did, it would actually - # create another thread and start executing it, and we'd no longer - # be single-threaded. - return thread - - def run_message_loop(self): - thread = self._threads()[0] - thread.run_in_main_thread(self._test_runner, - self._test_runner._current_result_summary) - self._test_runner.update() - - -class MultiThreadedBroker(WorkerMessageBroker): - def _start_worker(self, worker_number, worker_name): - thread = dump_render_tree_thread.TestShellThread(self._port, - self._options, worker_number, worker_name, - self._test_runner._current_filename_queue, - self._test_runner._result_queue) - thread.start() - return thread - - def run_message_loop(self): - threads = self._threads() - - # Loop through all the threads waiting for them to finish. - some_thread_is_alive = True - while some_thread_is_alive: - some_thread_is_alive = False - t = time.time() - for thread in threads: - exception_info = thread.exception_info() - if exception_info is not None: - # Re-raise the thread's exception here to make it - # clear that testing was aborted. Otherwise, - # the tests that did not run would be assumed - # to have passed. - raise exception_info[0], exception_info[1], exception_info[2] - - if thread.isAlive(): - some_thread_is_alive = True - next_timeout = thread.next_timeout() - if next_timeout and t > next_timeout: - log_wedged_worker(thread.getName(), thread.id()) - thread.clear_next_timeout() - - self._test_runner.update() - - if some_thread_is_alive: - time.sleep(0.01) - - def cancel_workers(self): - threads = self._threads() - for thread in threads: - thread.cancel() - - -def log_wedged_worker(name, id): - """Log information about the given worker state.""" - stack = _find_thread_stack(id) - assert(stack is not None) - _log.error("") - _log.error("%s (tid %d) is wedged" % (name, id)) - _log_stack(stack) - _log.error("") - - -def _find_thread_stack(id): - """Returns a stack object that can be used to dump a stack trace for - the given thread id (or None if the id is not found).""" - for thread_id, stack in sys._current_frames().items(): - if thread_id == id: - return stack - return None - - -def _log_stack(stack): - """Log a stack trace to log.error().""" - for filename, lineno, name, line in traceback.extract_stack(stack): - _log.error('File: "%s", line %d, in %s' % (filename, lineno, name)) - if line: - _log.error(' %s' % line.strip()) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/message_broker_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/message_broker_unittest.py deleted file mode 100644 index 6f04fd3..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/message_broker_unittest.py +++ /dev/null @@ -1,183 +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 Queue -import sys -import thread -import threading -import time -import unittest - -from webkitpy.common import array_stream -from webkitpy.common.system import outputcapture -from webkitpy.tool import mocktool - -from webkitpy.layout_tests import run_webkit_tests - -import message_broker - - -class TestThread(threading.Thread): - def __init__(self, started_queue, stopping_queue): - threading.Thread.__init__(self) - self._thread_id = None - self._started_queue = started_queue - self._stopping_queue = stopping_queue - self._timeout = False - self._timeout_queue = Queue.Queue() - self._exception_info = None - - def id(self): - return self._thread_id - - def getName(self): - return "worker-0" - - def run(self): - self._covered_run() - - def _covered_run(self): - # FIXME: this is a separate routine to work around a bug - # in coverage: see http://bitbucket.org/ned/coveragepy/issue/85. - self._thread_id = thread.get_ident() - try: - self._started_queue.put('') - msg = self._stopping_queue.get() - if msg == 'KeyboardInterrupt': - raise KeyboardInterrupt - elif msg == 'Exception': - raise ValueError() - elif msg == 'Timeout': - self._timeout = True - self._timeout_queue.get() - except: - self._exception_info = sys.exc_info() - - def exception_info(self): - return self._exception_info - - def next_timeout(self): - if self._timeout: - self._timeout_queue.put('done') - return time.time() - 10 - return time.time() - - def clear_next_timeout(self): - self._next_timeout = None - -class TestHandler(logging.Handler): - def __init__(self, astream): - logging.Handler.__init__(self) - self._stream = astream - - def emit(self, record): - self._stream.write(self.format(record)) - - -class MultiThreadedBrokerTest(unittest.TestCase): - class MockTestRunner(object): - def __init__(self): - pass - - def __del__(self): - pass - - def update(self): - pass - - def run_one_thread(self, msg): - runner = self.MockTestRunner() - port = None - options = mocktool.MockOptions(child_processes='1') - starting_queue = Queue.Queue() - stopping_queue = Queue.Queue() - broker = message_broker.MultiThreadedBroker(port, options) - broker._test_runner = runner - child_thread = TestThread(starting_queue, stopping_queue) - broker._workers['worker-0'] = message_broker._WorkerState('worker-0') - broker._workers['worker-0'].thread = child_thread - child_thread.start() - started_msg = starting_queue.get() - stopping_queue.put(msg) - return broker.run_message_loop() - - def test_basic(self): - interrupted = self.run_one_thread('') - self.assertFalse(interrupted) - - def test_interrupt(self): - self.assertRaises(KeyboardInterrupt, self.run_one_thread, 'KeyboardInterrupt') - - def test_timeout(self): - oc = outputcapture.OutputCapture() - oc.capture_output() - interrupted = self.run_one_thread('Timeout') - self.assertFalse(interrupted) - oc.restore_output() - - def test_exception(self): - self.assertRaises(ValueError, self.run_one_thread, 'Exception') - - -class Test(unittest.TestCase): - def test_find_thread_stack_found(self): - id, stack = sys._current_frames().items()[0] - found_stack = message_broker._find_thread_stack(id) - self.assertNotEqual(found_stack, None) - - def test_find_thread_stack_not_found(self): - found_stack = message_broker._find_thread_stack(0) - self.assertEqual(found_stack, None) - - def test_log_wedged_worker(self): - oc = outputcapture.OutputCapture() - oc.capture_output() - logger = message_broker._log - astream = array_stream.ArrayStream() - handler = TestHandler(astream) - logger.addHandler(handler) - - starting_queue = Queue.Queue() - stopping_queue = Queue.Queue() - child_thread = TestThread(starting_queue, stopping_queue) - child_thread.start() - msg = starting_queue.get() - - message_broker.log_wedged_worker(child_thread.getName(), - child_thread.id()) - stopping_queue.put('') - child_thread.join(timeout=1.0) - - self.assertFalse(astream.empty()) - self.assertFalse(child_thread.isAlive()) - oc.restore_output() - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream.py deleted file mode 100644 index 20646a1..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream.py +++ /dev/null @@ -1,146 +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. - -""" -Package that implements a stream wrapper that has 'meters' as well as -regular output. A 'meter' is a single line of text that can be erased -and rewritten repeatedly, without producing multiple lines of output. It -can be used to produce effects like progress bars. - -This package should only be called by the printing module in the layout_tests -package. -""" - -import logging - -_log = logging.getLogger("webkitpy.layout_tests.metered_stream") - - -class MeteredStream: - """This class is a wrapper around a stream that allows you to implement - meters (progress bars, etc.). - - It can be used directly as a stream, by calling write(), but provides - two other methods for output, update(), and progress(). - - In normal usage, update() will overwrite the output of the immediately - preceding update() (write() also will overwrite update()). So, calling - multiple update()s in a row can provide an updating status bar (note that - if an update string contains newlines, only the text following the last - newline will be overwritten/erased). - - If the MeteredStream is constructed in "verbose" mode (i.e., by passing - verbose=true), then update() no longer overwrite a previous update(), and - instead the call is equivalent to write(), although the text is - actually sent to the logger rather than to the stream passed - to the constructor. - - progress() is just like update(), except that if you are in verbose mode, - progress messages are not output at all (they are dropped). This is - used for things like progress bars which are presumed to be unwanted in - verbose mode. - - Note that the usual usage for this class is as a destination for - a logger that can also be written to directly (i.e., some messages go - through the logger, some don't). We thus have to dance around a - layering inversion in update() for things to work correctly. - """ - - def __init__(self, verbose, stream): - """ - Args: - verbose: whether progress is a no-op and updates() aren't overwritten - stream: output stream to write to - """ - self._dirty = False - self._verbose = verbose - self._stream = stream - self._last_update = "" - - def write(self, txt): - """Write to the stream, overwriting and resetting the meter.""" - if self._dirty: - self._write(txt) - self._dirty = False - self._last_update = '' - else: - self._stream.write(txt) - - def flush(self): - """Flush any buffered output.""" - self._stream.flush() - - def progress(self, str): - """ - Write a message to the stream that will get overwritten. - - This is used for progress updates that don't need to be preserved in - the log. If the MeteredStream was initialized with verbose==True, - then this output is discarded. We have this in case we are logging - lots of output and the update()s will get lost or won't work - properly (typically because verbose streams are redirected to files). - - """ - if self._verbose: - return - self._write(str) - - def update(self, str): - """ - Write a message that is also included when logging verbosely. - - This routine preserves the same console logging behavior as progress(), - but will also log the message if verbose() was true. - - """ - # Note this is a separate routine that calls either into the logger - # or the metering stream. We have to be careful to avoid a layering - # inversion (stream calling back into the logger). - if self._verbose: - _log.info(str) - else: - self._write(str) - - def _write(self, str): - """Actually write the message to the stream.""" - - # FIXME: Figure out if there is a way to detect if we're writing - # to a stream that handles CRs correctly (e.g., terminals). That might - # be a cleaner way of handling this. - - # Print the necessary number of backspaces to erase the previous - # message. - if len(self._last_update): - self._stream.write("\b" * len(self._last_update) + - " " * len(self._last_update) + - "\b" * len(self._last_update)) - self._stream.write(str) - last_newline = str.rfind("\n") - self._last_update = str[(last_newline + 1):] - self._dirty = True diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream_unittest.py deleted file mode 100644 index 9421ff8..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/metered_stream_unittest.py +++ /dev/null @@ -1,115 +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 metered_stream.py.""" - -import os -import optparse -import pdb -import sys -import unittest - -from webkitpy.common.array_stream import ArrayStream -from webkitpy.layout_tests.layout_package import metered_stream - - -class TestMeteredStream(unittest.TestCase): - def test_regular(self): - a = ArrayStream() - m = metered_stream.MeteredStream(verbose=False, stream=a) - self.assertTrue(a.empty()) - - # basic test - note that the flush() is a no-op, but we include it - # for coverage. - m.write("foo") - m.flush() - exp = ['foo'] - self.assertEquals(a.get(), exp) - - # now check that a second write() does not overwrite the first. - m.write("bar") - exp.append('bar') - self.assertEquals(a.get(), exp) - - m.update("batter") - exp.append('batter') - self.assertEquals(a.get(), exp) - - # The next update() should overwrite the laste update() but not the - # other text. Note that the cursor is effectively positioned at the - # end of 'foo', even though we had to erase three more characters. - m.update("foo") - exp.append('\b\b\b\b\b\b \b\b\b\b\b\b') - exp.append('foo') - self.assertEquals(a.get(), exp) - - m.progress("progress") - exp.append('\b\b\b \b\b\b') - exp.append('progress') - self.assertEquals(a.get(), exp) - - # now check that a write() does overwrite the progress bar - m.write("foo") - exp.append('\b\b\b\b\b\b\b\b \b\b\b\b\b\b\b\b') - exp.append('foo') - self.assertEquals(a.get(), exp) - - # Now test that we only back up to the most recent newline. - - # Note also that we do not back up to erase the most recent write(), - # i.e., write()s do not get erased. - a.reset() - m.update("foo\nbar") - m.update("baz") - self.assertEquals(a.get(), ['foo\nbar', '\b\b\b \b\b\b', 'baz']) - - def test_verbose(self): - a = ArrayStream() - m = metered_stream.MeteredStream(verbose=True, stream=a) - self.assertTrue(a.empty()) - m.write("foo") - self.assertEquals(a.get(), ['foo']) - - import logging - b = ArrayStream() - logger = logging.getLogger() - handler = logging.StreamHandler(b) - logger.addHandler(handler) - m.update("bar") - logger.handlers.remove(handler) - self.assertEquals(a.get(), ['foo']) - self.assertEquals(b.get(), ['bar\n']) - - m.progress("dropped") - self.assertEquals(a.get(), ['foo']) - self.assertEquals(b.get(), ['bar\n']) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py deleted file mode 100644 index 7a6aad1..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing.py +++ /dev/null @@ -1,553 +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. - -"""Package that handles non-debug, non-file output for run-webkit-tests.""" - -import logging -import optparse -import os -import pdb - -from webkitpy.layout_tests.layout_package import metered_stream -from webkitpy.layout_tests.layout_package import test_expectations - -_log = logging.getLogger("webkitpy.layout_tests.printer") - -TestExpectationsFile = test_expectations.TestExpectationsFile - -NUM_SLOW_TESTS_TO_LOG = 10 - -PRINT_DEFAULT = ("misc,one-line-progress,one-line-summary,unexpected," - "unexpected-results,updates") -PRINT_EVERYTHING = ("actual,config,expected,misc,one-line-progress," - "one-line-summary,slowest,timing,unexpected," - "unexpected-results,updates") - -HELP_PRINTING = """ -Output for run-webkit-tests is controlled by a comma-separated list of -values passed to --print. Values either influence the overall output, or -the output at the beginning of the run, during the run, or at the end: - -Overall options: - nothing don't print anything. This overrides every other option - default include the default options. This is useful for logging - the default options plus additional settings. - everything print everything (except the trace-* options and the - detailed-progress option, see below for the full list ) - misc print miscellaneous things like blank lines - -At the beginning of the run: - config print the test run configuration - expected print a summary of what is expected to happen - (# passes, # failures, etc.) - -During the run: - detailed-progress print one dot per test completed - one-line-progress print a one-line progress bar - unexpected print any unexpected results as they occur - updates print updates on which stage is executing - trace-everything print detailed info on every test's results - (baselines, expectation, time it took to run). If - this is specified it will override the '*-progress' - options, the 'trace-unexpected' option, and the - 'unexpected' option. - trace-unexpected like 'trace-everything', but only for tests with - unexpected results. If this option is specified, - it will override the 'unexpected' option. - -At the end of the run: - actual print a summary of the actual results - slowest print %(slowest)d slowest tests and the time they took - timing print timing statistics - unexpected-results print a list of the tests with unexpected results - one-line-summary print a one-line summary of the run - -Notes: - - 'detailed-progress' can only be used if running in a single thread - (using --child-processes=1) or a single queue of tests (using - --experimental-fully-parallel). If these conditions aren't true, - 'one-line-progress' will be used instead. - - If both 'detailed-progress' and 'one-line-progress' are specified (and - both are possible), 'detailed-progress' will be used. - - If 'nothing' is specified, it overrides all of the other options. - - Specifying --verbose is equivalent to --print everything plus it - changes the format of the log messages to add timestamps and other - information. If you specify --verbose and --print X, then X overrides - the --print everything implied by --verbose. - ---print 'everything' is equivalent to --print '%(everything)s'. - -The default (--print default) is equivalent to --print '%(default)s'. -""" % {'slowest': NUM_SLOW_TESTS_TO_LOG, 'everything': PRINT_EVERYTHING, - 'default': PRINT_DEFAULT} - - -def print_options(): - return [ - # Note: We use print_options rather than just 'print' because print - # is a reserved word. - # Note: Also, we don't specify a default value so we can detect when - # no flag is specified on the command line and use different defaults - # based on whether or not --verbose is specified (since --print - # overrides --verbose). - optparse.make_option("--print", dest="print_options", - help=("controls print output of test run. " - "Use --help-printing for more.")), - optparse.make_option("--help-printing", action="store_true", - help="show detailed help on controlling print output"), - optparse.make_option("-v", "--verbose", action="store_true", - default=False, help="include debug-level logging"), - ] - - -def parse_print_options(print_options, verbose, child_processes, - is_fully_parallel): - """Parse the options provided to --print and dedup and rank them. - - Returns - a set() of switches that govern how logging is done - - """ - if print_options: - switches = set(print_options.split(',')) - elif verbose: - switches = set(PRINT_EVERYTHING.split(',')) - else: - switches = set(PRINT_DEFAULT.split(',')) - - if 'nothing' in switches: - return set() - - if (child_processes != 1 and not is_fully_parallel and - 'detailed-progress' in switches): - _log.warn("Can only print 'detailed-progress' if running " - "with --child-processes=1 or " - "with --experimental-fully-parallel. " - "Using 'one-line-progress' instead.") - switches.discard('detailed-progress') - switches.add('one-line-progress') - - if 'everything' in switches: - switches.discard('everything') - switches.update(set(PRINT_EVERYTHING.split(','))) - - if 'default' in switches: - switches.discard('default') - switches.update(set(PRINT_DEFAULT.split(','))) - - if 'detailed-progress' in switches: - switches.discard('one-line-progress') - - if 'trace-everything' in switches: - switches.discard('detailed-progress') - switches.discard('one-line-progress') - switches.discard('trace-unexpected') - switches.discard('unexpected') - - if 'trace-unexpected' in switches: - switches.discard('unexpected') - - return switches - - -def _configure_logging(stream, verbose): - log_fmt = '%(message)s' - log_datefmt = '%y%m%d %H:%M:%S' - log_level = logging.INFO - if verbose: - log_fmt = ('%(asctime)s %(process)d %(filename)s:%(lineno)d ' - '%(levelname)s %(message)s') - log_level = logging.DEBUG - - root = logging.getLogger() - handler = logging.StreamHandler(stream) - handler.setFormatter(logging.Formatter(log_fmt, None)) - root.addHandler(handler) - root.setLevel(log_level) - return handler - - -def _restore_logging(handler_to_remove): - root = logging.getLogger() - root.handlers.remove(handler_to_remove) - - -class Printer(object): - """Class handling all non-debug-logging printing done by run-webkit-tests. - - Printing from run-webkit-tests falls into two buckets: general or - regular output that is read only by humans and can be changed at any - time, and output that is parsed by buildbots (and humans) and hence - must be changed more carefully and in coordination with the buildbot - parsing code (in chromium.org's buildbot/master.chromium/scripts/master/ - log_parser/webkit_test_command.py script). - - By default the buildbot-parsed code gets logged to stdout, and regular - output gets logged to stderr.""" - def __init__(self, port, options, regular_output, buildbot_output, - child_processes, is_fully_parallel): - """ - Args - port interface to port-specific routines - options OptionParser object with command line settings - regular_output stream to which output intended only for humans - should be written - buildbot_output stream to which output intended to be read by - the buildbots (and humans) should be written - child_processes number of parallel threads running (usually - controlled by --child-processes) - is_fully_parallel are the tests running in a single queue, or - in shards (usually controlled by - --experimental-fully-parallel) - - Note that the last two args are separate rather than bundled into - the options structure so that this object does not assume any flags - set in options that weren't returned from logging_options(), above. - The two are used to determine whether or not we can sensibly use - the 'detailed-progress' option, or can only use 'one-line-progress'. - """ - self._buildbot_stream = buildbot_output - self._options = options - self._port = port - self._stream = regular_output - - # These are used for --print detailed-progress to track status by - # directory. - self._current_dir = None - self._current_progress_str = "" - self._current_test_number = 0 - - self._meter = metered_stream.MeteredStream(options.verbose, - regular_output) - self._logging_handler = _configure_logging(self._meter, - options.verbose) - - self.switches = parse_print_options(options.print_options, - options.verbose, child_processes, is_fully_parallel) - - def cleanup(self): - """Restore logging configuration to its initial settings.""" - if self._logging_handler: - _restore_logging(self._logging_handler) - self._logging_handler = None - - def __del__(self): - self.cleanup() - - # These two routines just hide the implementation of the switches. - def disabled(self, option): - return not option in self.switches - - def enabled(self, option): - return option in self.switches - - def help_printing(self): - self._write(HELP_PRINTING) - - def print_actual(self, msg): - if self.disabled('actual'): - return - self._buildbot_stream.write("%s\n" % msg) - - def print_config(self, msg): - self.write(msg, 'config') - - def print_expected(self, msg): - self.write(msg, 'expected') - - def print_timing(self, msg): - self.write(msg, 'timing') - - def print_one_line_summary(self, total, expected, unexpected): - """Print a one-line summary of the test run to stdout. - - Args: - total: total number of tests run - expected: number of expected results - unexpected: number of unexpected results - """ - if self.disabled('one-line-summary'): - return - - incomplete = total - expected - unexpected - if incomplete: - self._write("") - incomplete_str = " (%d didn't run)" % incomplete - expected_str = str(expected) - else: - incomplete_str = "" - expected_str = "All %d" % expected - - if unexpected == 0: - self._write("%s tests ran as expected%s." % - (expected_str, incomplete_str)) - elif expected == 1: - self._write("1 test ran as expected, %d didn't%s:" % - (unexpected, incomplete_str)) - else: - self._write("%d tests ran as expected, %d didn't%s:" % - (expected, unexpected, incomplete_str)) - self._write("") - - def print_test_result(self, result, expected, exp_str, got_str): - """Print the result of the test as determined by --print. - - This routine is used to print the details of each test as it completes. - - Args: - result - The actual TestResult object - expected - Whether the result we got was an expected result - exp_str - What we expected to get (used for tracing) - got_str - What we actually got (used for tracing) - - Note that we need all of these arguments even though they seem - somewhat redundant, in order to keep this routine from having to - known anything about the set of expectations. - """ - if (self.enabled('trace-everything') or - self.enabled('trace-unexpected') and not expected): - self._print_test_trace(result, exp_str, got_str) - elif (not expected and self.enabled('unexpected') and - self.disabled('detailed-progress')): - # Note: 'detailed-progress' handles unexpected results internally, - # so we skip it here. - self._print_unexpected_test_result(result) - - def _print_test_trace(self, result, exp_str, got_str): - """Print detailed results of a test (triggered by --print trace-*). - For each test, print: - - location of the expected baselines - - expected results - - actual result - - timing info - """ - filename = result.filename - test_name = self._port.relative_test_filename(filename) - self._write('trace: %s' % test_name) - txt_file = self._port.expected_filename(filename, '.txt') - if self._port.path_exists(txt_file): - self._write(' txt: %s' % - self._port.relative_test_filename(txt_file)) - else: - self._write(' txt: <none>') - checksum_file = self._port.expected_filename(filename, '.checksum') - if self._port.path_exists(checksum_file): - self._write(' sum: %s' % - self._port.relative_test_filename(checksum_file)) - else: - self._write(' sum: <none>') - png_file = self._port.expected_filename(filename, '.png') - if self._port.path_exists(png_file): - self._write(' png: %s' % - self._port.relative_test_filename(png_file)) - else: - self._write(' png: <none>') - self._write(' exp: %s' % exp_str) - self._write(' got: %s' % got_str) - self._write(' took: %-.3f' % result.test_run_time) - self._write('') - - def _print_unexpected_test_result(self, result): - """Prints one unexpected test result line.""" - desc = TestExpectationsFile.EXPECTATION_DESCRIPTIONS[result.type][0] - self.write(" %s -> unexpected %s" % - (self._port.relative_test_filename(result.filename), - desc), "unexpected") - - def print_progress(self, result_summary, retrying, test_list): - """Print progress through the tests as determined by --print.""" - if self.enabled('detailed-progress'): - self._print_detailed_progress(result_summary, test_list) - elif self.enabled('one-line-progress'): - self._print_one_line_progress(result_summary, retrying) - else: - return - - if result_summary.remaining == 0: - self._meter.update('') - - def _print_one_line_progress(self, result_summary, retrying): - """Displays the progress through the test run.""" - percent_complete = 100 * (result_summary.expected + - result_summary.unexpected) / result_summary.total - action = "Testing" - if retrying: - action = "Retrying" - self._meter.progress("%s (%d%%): %d ran as expected, %d didn't," - " %d left" % (action, percent_complete, result_summary.expected, - result_summary.unexpected, result_summary.remaining)) - - def _print_detailed_progress(self, result_summary, test_list): - """Display detailed progress output where we print the directory name - and one dot for each completed test. This is triggered by - "--log detailed-progress".""" - if self._current_test_number == len(test_list): - return - - next_test = test_list[self._current_test_number] - next_dir = os.path.dirname( - self._port.relative_test_filename(next_test)) - if self._current_progress_str == "": - self._current_progress_str = "%s: " % (next_dir) - self._current_dir = next_dir - - while next_test in result_summary.results: - if next_dir != self._current_dir: - self._meter.write("%s\n" % (self._current_progress_str)) - self._current_progress_str = "%s: ." % (next_dir) - self._current_dir = next_dir - else: - self._current_progress_str += "." - - if (next_test in result_summary.unexpected_results and - self.enabled('unexpected')): - self._meter.write("%s\n" % self._current_progress_str) - test_result = result_summary.results[next_test] - self._print_unexpected_test_result(test_result) - self._current_progress_str = "%s: " % self._current_dir - - self._current_test_number += 1 - if self._current_test_number == len(test_list): - break - - next_test = test_list[self._current_test_number] - next_dir = os.path.dirname( - self._port.relative_test_filename(next_test)) - - if result_summary.remaining: - remain_str = " (%d)" % (result_summary.remaining) - self._meter.progress("%s%s" % (self._current_progress_str, - remain_str)) - else: - self._meter.progress("%s" % (self._current_progress_str)) - - def print_unexpected_results(self, unexpected_results): - """Prints a list of the unexpected results to the buildbot stream.""" - if self.disabled('unexpected-results'): - return - - passes = {} - flaky = {} - regressions = {} - - for test, results in unexpected_results['tests'].iteritems(): - actual = results['actual'].split(" ") - expected = results['expected'].split(" ") - if actual == ['PASS']: - if 'CRASH' in expected: - _add_to_dict_of_lists(passes, - 'Expected to crash, but passed', - test) - elif 'TIMEOUT' in expected: - _add_to_dict_of_lists(passes, - 'Expected to timeout, but passed', - test) - else: - _add_to_dict_of_lists(passes, - 'Expected to fail, but passed', - test) - elif len(actual) > 1: - # We group flaky tests by the first actual result we got. - _add_to_dict_of_lists(flaky, actual[0], test) - else: - _add_to_dict_of_lists(regressions, results['actual'], test) - - if len(passes) or len(flaky) or len(regressions): - self._buildbot_stream.write("\n") - - if len(passes): - for key, tests in passes.iteritems(): - self._buildbot_stream.write("%s: (%d)\n" % (key, len(tests))) - tests.sort() - for test in tests: - self._buildbot_stream.write(" %s\n" % test) - self._buildbot_stream.write("\n") - self._buildbot_stream.write("\n") - - if len(flaky): - descriptions = TestExpectationsFile.EXPECTATION_DESCRIPTIONS - for key, tests in flaky.iteritems(): - result = TestExpectationsFile.EXPECTATIONS[key.lower()] - self._buildbot_stream.write("Unexpected flakiness: %s (%d)\n" - % (descriptions[result][1], len(tests))) - tests.sort() - - for test in tests: - result = unexpected_results['tests'][test] - actual = result['actual'].split(" ") - expected = result['expected'].split(" ") - result = TestExpectationsFile.EXPECTATIONS[key.lower()] - new_expectations_list = list(set(actual) | set(expected)) - self._buildbot_stream.write(" %s = %s\n" % - (test, " ".join(new_expectations_list))) - self._buildbot_stream.write("\n") - self._buildbot_stream.write("\n") - - if len(regressions): - descriptions = TestExpectationsFile.EXPECTATION_DESCRIPTIONS - for key, tests in regressions.iteritems(): - result = TestExpectationsFile.EXPECTATIONS[key.lower()] - self._buildbot_stream.write( - "Regressions: Unexpected %s : (%d)\n" % ( - descriptions[result][1], len(tests))) - tests.sort() - for test in tests: - self._buildbot_stream.write(" %s = %s\n" % (test, key)) - self._buildbot_stream.write("\n") - self._buildbot_stream.write("\n") - - if len(unexpected_results['tests']) and self._options.verbose: - self._buildbot_stream.write("%s\n" % ("-" * 78)) - - def print_update(self, msg): - if self.disabled('updates'): - return - self._meter.update(msg) - - def write(self, msg, option="misc"): - if self.disabled(option): - return - self._write(msg) - - def _write(self, msg): - # FIXME: we could probably get away with calling _log.info() all of - # the time, but there doesn't seem to be a good way to test the output - # from the logger :(. - if self._options.verbose: - _log.info(msg) - else: - self._meter.write("%s\n" % msg) - -# -# Utility routines used by the Controller class -# - - -def _add_to_dict_of_lists(dict, key, value): - dict.setdefault(key, []).append(value) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py deleted file mode 100644 index 27a6a29..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/printing_unittest.py +++ /dev/null @@ -1,606 +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 printing.py.""" - -import os -import optparse -import pdb -import sys -import unittest -import logging - -from webkitpy.common import array_stream -from webkitpy.common.system import logtesting -from webkitpy.layout_tests import port -from webkitpy.layout_tests.layout_package import printing -from webkitpy.layout_tests.layout_package import test_results -from webkitpy.layout_tests.layout_package import test_expectations -from webkitpy.layout_tests.layout_package import test_failures -from webkitpy.layout_tests import run_webkit_tests - - -def get_options(args): - print_options = printing.print_options() - option_parser = optparse.OptionParser(option_list=print_options) - return option_parser.parse_args(args) - - -class TestUtilityFunctions(unittest.TestCase): - def test_configure_logging(self): - options, args = get_options([]) - stream = array_stream.ArrayStream() - handler = printing._configure_logging(stream, options.verbose) - logging.info("this should be logged") - self.assertFalse(stream.empty()) - - stream.reset() - logging.debug("this should not be logged") - self.assertTrue(stream.empty()) - - printing._restore_logging(handler) - - stream.reset() - options, args = get_options(['--verbose']) - handler = printing._configure_logging(stream, options.verbose) - logging.debug("this should be logged") - self.assertFalse(stream.empty()) - printing._restore_logging(handler) - - def test_print_options(self): - options, args = get_options([]) - self.assertTrue(options is not None) - - def test_parse_print_options(self): - def test_switches(args, expected_switches_str, - verbose=False, child_processes=1, - is_fully_parallel=False): - options, args = get_options(args) - if expected_switches_str: - expected_switches = set(expected_switches_str.split(',')) - else: - expected_switches = set() - switches = printing.parse_print_options(options.print_options, - verbose, - child_processes, - is_fully_parallel) - self.assertEqual(expected_switches, switches) - - # test that we default to the default set of switches - test_switches([], printing.PRINT_DEFAULT) - - # test that verbose defaults to everything - test_switches([], printing.PRINT_EVERYTHING, verbose=True) - - # test that --print default does what it's supposed to - test_switches(['--print', 'default'], printing.PRINT_DEFAULT) - - # test that --print nothing does what it's supposed to - test_switches(['--print', 'nothing'], None) - - # test that --print everything does what it's supposed to - test_switches(['--print', 'everything'], printing.PRINT_EVERYTHING) - - # this tests that '--print X' overrides '--verbose' - test_switches(['--print', 'actual'], 'actual', verbose=True) - - - -class Testprinter(unittest.TestCase): - def get_printer(self, args=None, single_threaded=False, - is_fully_parallel=False): - printing_options = printing.print_options() - option_parser = optparse.OptionParser(option_list=printing_options) - options, args = option_parser.parse_args(args) - self._port = port.get('test', options) - nproc = 2 - if single_threaded: - nproc = 1 - - regular_output = array_stream.ArrayStream() - buildbot_output = array_stream.ArrayStream() - printer = printing.Printer(self._port, options, regular_output, - buildbot_output, single_threaded, - is_fully_parallel) - return printer, regular_output, buildbot_output - - def get_result(self, test, result_type=test_expectations.PASS, run_time=0): - failures = [] - if result_type == test_expectations.TIMEOUT: - failures = [test_failures.FailureTimeout()] - elif result_type == test_expectations.CRASH: - failures = [test_failures.FailureCrash()] - path = os.path.join(self._port.layout_tests_dir(), test) - return test_results.TestResult(path, failures, run_time, - total_time_for_all_diffs=0, - time_for_diffs=0) - - def get_result_summary(self, tests, expectations_str): - test_paths = [os.path.join(self._port.layout_tests_dir(), test) for - test in tests] - expectations = test_expectations.TestExpectations( - self._port, test_paths, expectations_str, - self._port.test_platform_name(), is_debug_mode=False, - is_lint_mode=False) - - rs = run_webkit_tests.ResultSummary(expectations, test_paths) - return test_paths, rs, expectations - - def test_help_printer(self): - # Here and below we'll call the "regular" printer err and the - # buildbot printer out; this corresponds to how things run on the - # bots with stderr and stdout. - printer, err, out = self.get_printer() - - # This routine should print something to stdout. testing what it is - # is kind of pointless. - printer.help_printing() - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - def do_switch_tests(self, method_name, switch, to_buildbot, - message='hello', exp_err=None, exp_bot=None): - def do_helper(method_name, switch, message, exp_err, exp_bot): - printer, err, bot = self.get_printer(['--print', switch]) - getattr(printer, method_name)(message) - self.assertEqual(err.get(), exp_err) - self.assertEqual(bot.get(), exp_bot) - - if to_buildbot: - if exp_err is None: - exp_err = [] - if exp_bot is None: - exp_bot = [message + "\n"] - else: - if exp_err is None: - exp_err = [message + "\n"] - if exp_bot is None: - exp_bot = [] - do_helper(method_name, 'nothing', 'hello', [], []) - do_helper(method_name, switch, 'hello', exp_err, exp_bot) - do_helper(method_name, 'everything', 'hello', exp_err, exp_bot) - - def test_configure_and_cleanup(self): - # This test verifies that calling cleanup repeatedly and deleting - # the object is safe. - printer, err, out = self.get_printer(['--print', 'everything']) - printer.cleanup() - printer.cleanup() - printer = None - - def test_print_actual(self): - # Actual results need to be logged to the buildbot's stream. - self.do_switch_tests('print_actual', 'actual', to_buildbot=True) - - def test_print_actual_buildbot(self): - # FIXME: Test that the format of the actual results matches what the - # buildbot is expecting. - pass - - def test_print_config(self): - self.do_switch_tests('print_config', 'config', to_buildbot=False) - - def test_print_expected(self): - self.do_switch_tests('print_expected', 'expected', to_buildbot=False) - - def test_print_timing(self): - self.do_switch_tests('print_timing', 'timing', to_buildbot=False) - - def test_print_update(self): - # Note that there shouldn't be a carriage return here; updates() - # are meant to be overwritten. - self.do_switch_tests('print_update', 'updates', to_buildbot=False, - message='hello', exp_err=['hello']) - - def test_print_one_line_summary(self): - printer, err, out = self.get_printer(['--print', 'nothing']) - printer.print_one_line_summary(1, 1, 0) - self.assertTrue(err.empty()) - - printer, err, out = self.get_printer(['--print', 'one-line-summary']) - printer.print_one_line_summary(1, 1, 0) - self.assertEquals(err.get(), ["All 1 tests ran as expected.\n", "\n"]) - - printer, err, out = self.get_printer(['--print', 'everything']) - printer.print_one_line_summary(1, 1, 0) - self.assertEquals(err.get(), ["All 1 tests ran as expected.\n", "\n"]) - - err.reset() - printer.print_one_line_summary(2, 1, 1) - self.assertEquals(err.get(), - ["1 test ran as expected, 1 didn't:\n", "\n"]) - - err.reset() - printer.print_one_line_summary(3, 2, 1) - self.assertEquals(err.get(), - ["2 tests ran as expected, 1 didn't:\n", "\n"]) - - err.reset() - printer.print_one_line_summary(3, 2, 0) - self.assertEquals(err.get(), - ['\n', "2 tests ran as expected (1 didn't run).\n", - '\n']) - - - def test_print_test_result(self): - # Note here that we don't use meaningful exp_str and got_str values; - # the actual contents of the string are treated opaquely by - # print_test_result() when tracing, and usually we don't want - # to test what exactly is printed, just that something - # was printed (or that nothing was printed). - # - # FIXME: this is actually some goofy layering; it would be nice - # we could refactor it so that the args weren't redundant. Maybe - # the TestResult should contain what was expected, and the - # strings could be derived from the TestResult? - printer, err, out = self.get_printer(['--print', 'nothing']) - result = self.get_result('passes/image.html') - printer.print_test_result(result, expected=False, exp_str='', - got_str='') - self.assertTrue(err.empty()) - - printer, err, out = self.get_printer(['--print', 'unexpected']) - printer.print_test_result(result, expected=True, exp_str='', - got_str='') - self.assertTrue(err.empty()) - printer.print_test_result(result, expected=False, exp_str='', - got_str='') - self.assertEquals(err.get(), - [' passes/image.html -> unexpected pass\n']) - - printer, err, out = self.get_printer(['--print', 'everything']) - printer.print_test_result(result, expected=True, exp_str='', - got_str='') - self.assertTrue(err.empty()) - - printer.print_test_result(result, expected=False, exp_str='', - got_str='') - self.assertEquals(err.get(), - [' passes/image.html -> unexpected pass\n']) - - printer, err, out = self.get_printer(['--print', 'nothing']) - printer.print_test_result(result, expected=False, exp_str='', - got_str='') - self.assertTrue(err.empty()) - - printer, err, out = self.get_printer(['--print', - 'trace-unexpected']) - printer.print_test_result(result, expected=True, exp_str='', - got_str='') - self.assertTrue(err.empty()) - - printer, err, out = self.get_printer(['--print', - 'trace-unexpected']) - printer.print_test_result(result, expected=False, exp_str='', - got_str='') - self.assertFalse(err.empty()) - - printer, err, out = self.get_printer(['--print', - 'trace-unexpected']) - result = self.get_result("passes/text.html") - printer.print_test_result(result, expected=False, exp_str='', - got_str='') - self.assertFalse(err.empty()) - - err.reset() - printer.print_test_result(result, expected=False, exp_str='', - got_str='') - self.assertFalse(err.empty()) - - printer, err, out = self.get_printer(['--print', 'trace-everything']) - result = self.get_result('passes/image.html') - printer.print_test_result(result, expected=True, exp_str='', - got_str='') - result = self.get_result('failures/expected/missing_text.html') - printer.print_test_result(result, expected=True, exp_str='', - got_str='') - result = self.get_result('failures/expected/missing_check.html') - printer.print_test_result(result, expected=True, exp_str='', - got_str='') - result = self.get_result('failures/expected/missing_image.html') - printer.print_test_result(result, expected=True, exp_str='', - got_str='') - self.assertFalse(err.empty()) - - err.reset() - printer.print_test_result(result, expected=False, exp_str='', - got_str='') - - def test_print_progress(self): - expectations = '' - - # test that we print nothing - printer, err, out = self.get_printer(['--print', 'nothing']) - tests = ['passes/text.html', 'failures/expected/timeout.html', - 'failures/expected/crash.html'] - paths, rs, exp = self.get_result_summary(tests, expectations) - - printer.print_progress(rs, False, paths) - self.assertTrue(out.empty()) - self.assertTrue(err.empty()) - - printer.print_progress(rs, True, paths) - self.assertTrue(out.empty()) - self.assertTrue(err.empty()) - - # test regular functionality - printer, err, out = self.get_printer(['--print', - 'one-line-progress']) - printer.print_progress(rs, False, paths) - self.assertTrue(out.empty()) - self.assertFalse(err.empty()) - - err.reset() - out.reset() - printer.print_progress(rs, True, paths) - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - def test_print_progress__detailed(self): - tests = ['passes/text.html', 'failures/expected/timeout.html', - 'failures/expected/crash.html'] - expectations = 'failures/expected/timeout.html = TIMEOUT' - - # first, test that it is disabled properly - # should still print one-line-progress - printer, err, out = self.get_printer( - ['--print', 'detailed-progress'], single_threaded=False) - paths, rs, exp = self.get_result_summary(tests, expectations) - printer.print_progress(rs, False, paths) - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - # now test the enabled paths - printer, err, out = self.get_printer( - ['--print', 'detailed-progress'], single_threaded=True) - paths, rs, exp = self.get_result_summary(tests, expectations) - printer.print_progress(rs, False, paths) - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - err.reset() - out.reset() - printer.print_progress(rs, True, paths) - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - rs.add(self.get_result('passes/text.html', test_expectations.TIMEOUT), False) - rs.add(self.get_result('failures/expected/timeout.html'), True) - rs.add(self.get_result('failures/expected/crash.html', test_expectations.CRASH), True) - err.reset() - out.reset() - printer.print_progress(rs, False, paths) - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - # We only clear the meter when retrying w/ detailed-progress. - err.reset() - out.reset() - printer.print_progress(rs, True, paths) - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - printer, err, out = self.get_printer( - ['--print', 'detailed-progress,unexpected'], single_threaded=True) - paths, rs, exp = self.get_result_summary(tests, expectations) - printer.print_progress(rs, False, paths) - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - err.reset() - out.reset() - printer.print_progress(rs, True, paths) - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - rs.add(self.get_result('passes/text.html', test_expectations.TIMEOUT), False) - rs.add(self.get_result('failures/expected/timeout.html'), True) - rs.add(self.get_result('failures/expected/crash.html', test_expectations.CRASH), True) - err.reset() - out.reset() - printer.print_progress(rs, False, paths) - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - # We only clear the meter when retrying w/ detailed-progress. - err.reset() - out.reset() - printer.print_progress(rs, True, paths) - self.assertFalse(err.empty()) - self.assertTrue(out.empty()) - - def test_write_nothing(self): - printer, err, out = self.get_printer(['--print', 'nothing']) - printer.write("foo") - self.assertTrue(err.empty()) - - def test_write_misc(self): - printer, err, out = self.get_printer(['--print', 'misc']) - printer.write("foo") - self.assertFalse(err.empty()) - err.reset() - printer.write("foo", "config") - self.assertTrue(err.empty()) - - def test_write_everything(self): - printer, err, out = self.get_printer(['--print', 'everything']) - printer.write("foo") - self.assertFalse(err.empty()) - err.reset() - printer.write("foo", "config") - self.assertFalse(err.empty()) - - def test_write_verbose(self): - printer, err, out = self.get_printer(['--verbose']) - printer.write("foo") - self.assertTrue(not err.empty() and "foo" in err.get()[0]) - self.assertTrue(out.empty()) - - def test_print_unexpected_results(self): - # This routine is the only one that prints stuff that the bots - # care about. - # - # FIXME: there's some weird layering going on here. It seems - # like we shouldn't be both using an expectations string and - # having to specify whether or not the result was expected. - # This whole set of tests should probably be rewritten. - # - # FIXME: Plus, the fact that we're having to call into - # run_webkit_tests is clearly a layering inversion. - def get_unexpected_results(expected, passing, flaky): - """Return an unexpected results summary matching the input description. - - There are a lot of different combinations of test results that - can be tested; this routine produces various combinations based - on the values of the input flags. - - Args - expected: whether the tests ran as expected - passing: whether the tests should all pass - flaky: whether the tests should be flaky (if False, they - produce the same results on both runs; if True, they - all pass on the second run). - - """ - paths, rs, exp = self.get_result_summary(tests, expectations) - if expected: - rs.add(self.get_result('passes/text.html', test_expectations.PASS), - expected) - rs.add(self.get_result('failures/expected/timeout.html', - test_expectations.TIMEOUT), expected) - rs.add(self.get_result('failures/expected/crash.html', test_expectations.CRASH), - expected) - elif passing: - rs.add(self.get_result('passes/text.html'), expected) - rs.add(self.get_result('failures/expected/timeout.html'), expected) - rs.add(self.get_result('failures/expected/crash.html'), expected) - else: - rs.add(self.get_result('passes/text.html', test_expectations.TIMEOUT), - expected) - rs.add(self.get_result('failures/expected/timeout.html', - test_expectations.CRASH), expected) - rs.add(self.get_result('failures/expected/crash.html', - test_expectations.TIMEOUT), - expected) - retry = rs - if flaky: - paths, retry, exp = self.get_result_summary(tests, - expectations) - retry.add(self.get_result('passes/text.html'), True) - retry.add(self.get_result('failures/expected/timeout.html'), True) - retry.add(self.get_result('failures/expected/crash.html'), True) - unexpected_results = run_webkit_tests.summarize_unexpected_results( - self._port, exp, rs, retry) - return unexpected_results - - tests = ['passes/text.html', 'failures/expected/timeout.html', - 'failures/expected/crash.html'] - expectations = '' - - printer, err, out = self.get_printer(['--print', 'nothing']) - ur = get_unexpected_results(expected=False, passing=False, flaky=False) - printer.print_unexpected_results(ur) - self.assertTrue(err.empty()) - self.assertTrue(out.empty()) - - printer, err, out = self.get_printer(['--print', - 'unexpected-results']) - - # test everything running as expected - ur = get_unexpected_results(expected=True, passing=False, flaky=False) - printer.print_unexpected_results(ur) - self.assertTrue(err.empty()) - self.assertTrue(out.empty()) - - # test failures - err.reset() - out.reset() - ur = get_unexpected_results(expected=False, passing=False, flaky=False) - printer.print_unexpected_results(ur) - self.assertTrue(err.empty()) - self.assertFalse(out.empty()) - - # test unexpected flaky results - err.reset() - out.reset() - ur = get_unexpected_results(expected=False, passing=True, flaky=False) - printer.print_unexpected_results(ur) - self.assertTrue(err.empty()) - self.assertFalse(out.empty()) - - # test unexpected passes - err.reset() - out.reset() - ur = get_unexpected_results(expected=False, passing=False, flaky=True) - printer.print_unexpected_results(ur) - self.assertTrue(err.empty()) - self.assertFalse(out.empty()) - - err.reset() - out.reset() - printer, err, out = self.get_printer(['--print', 'everything']) - ur = get_unexpected_results(expected=False, passing=False, flaky=False) - printer.print_unexpected_results(ur) - self.assertTrue(err.empty()) - self.assertFalse(out.empty()) - - expectations = """ -failures/expected/crash.html = CRASH -failures/expected/timeout.html = TIMEOUT -""" - err.reset() - out.reset() - ur = get_unexpected_results(expected=False, passing=False, flaky=False) - printer.print_unexpected_results(ur) - self.assertTrue(err.empty()) - self.assertFalse(out.empty()) - - err.reset() - out.reset() - ur = get_unexpected_results(expected=False, passing=True, flaky=False) - printer.print_unexpected_results(ur) - self.assertTrue(err.empty()) - self.assertFalse(out.empty()) - - # Test handling of --verbose as well. - err.reset() - out.reset() - printer, err, out = self.get_printer(['--verbose']) - ur = get_unexpected_results(expected=False, passing=False, flaky=False) - printer.print_unexpected_results(ur) - self.assertTrue(err.empty()) - self.assertFalse(out.empty()) - - def test_print_unexpected_results_buildbot(self): - # FIXME: Test that print_unexpected_results() produces the printer the - # buildbot is expecting. - pass - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py deleted file mode 100644 index 67873a8..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations.py +++ /dev/null @@ -1,843 +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. - -"""A helper class for reading in and dealing with tests expectations -for layout tests. -""" - -import logging -import os -import re -import sys - -import webkitpy.thirdparty.simplejson as simplejson - -_log = logging.getLogger("webkitpy.layout_tests.layout_package." - "test_expectations") - -# Test expectation and modifier constants. -(PASS, FAIL, TEXT, IMAGE, IMAGE_PLUS_TEXT, TIMEOUT, CRASH, SKIP, WONTFIX, - SLOW, REBASELINE, MISSING, FLAKY, NOW, NONE) = range(15) - -# Test expectation file update action constants -(NO_CHANGE, REMOVE_TEST, REMOVE_PLATFORM, ADD_PLATFORMS_EXCEPT_THIS) = range(4) - - -def result_was_expected(result, expected_results, test_needs_rebaselining, - test_is_skipped): - """Returns whether we got a result we were expecting. - Args: - result: actual result of a test execution - expected_results: set of results listed in test_expectations - test_needs_rebaselining: whether test was marked as REBASELINE - test_is_skipped: whether test was marked as SKIP""" - if result in expected_results: - return True - if result in (IMAGE, TEXT, IMAGE_PLUS_TEXT) and FAIL in expected_results: - return True - if result == MISSING and test_needs_rebaselining: - return True - if result == SKIP and test_is_skipped: - return True - return False - - -def remove_pixel_failures(expected_results): - """Returns a copy of the expected results for a test, except that we - drop any pixel failures and return the remaining expectations. For example, - if we're not running pixel tests, then tests expected to fail as IMAGE - will PASS.""" - expected_results = expected_results.copy() - if IMAGE in expected_results: - expected_results.remove(IMAGE) - expected_results.add(PASS) - if IMAGE_PLUS_TEXT in expected_results: - expected_results.remove(IMAGE_PLUS_TEXT) - expected_results.add(TEXT) - return expected_results - - -class TestExpectations: - TEST_LIST = "test_expectations.txt" - - def __init__(self, port, tests, expectations, test_platform_name, - is_debug_mode, is_lint_mode, overrides=None): - """Loads and parses the test expectations given in the string. - Args: - port: handle to object containing platform-specific functionality - test: list of all of the test files - expectations: test expectations as a string - test_platform_name: name of the platform to match expectations - against. Note that this may be different than - port.test_platform_name() when is_lint_mode is True. - is_debug_mode: whether to use the DEBUG or RELEASE modifiers - in the expectations - is_lint_mode: If True, just parse the expectations string - looking for errors. - overrides: test expectations that are allowed to override any - entries in |expectations|. This is used by callers - that need to manage two sets of expectations (e.g., upstream - and downstream expectations). - """ - self._expected_failures = TestExpectationsFile(port, expectations, - tests, test_platform_name, is_debug_mode, is_lint_mode, - overrides=overrides) - - # TODO(ojan): Allow for removing skipped tests when getting the list of - # tests to run, but not when getting metrics. - # TODO(ojan): Replace the Get* calls here with the more sane API exposed - # by TestExpectationsFile below. Maybe merge the two classes entirely? - - def get_expectations_json_for_all_platforms(self): - return ( - self._expected_failures.get_expectations_json_for_all_platforms()) - - def get_rebaselining_failures(self): - return (self._expected_failures.get_test_set(REBASELINE, FAIL) | - self._expected_failures.get_test_set(REBASELINE, IMAGE) | - self._expected_failures.get_test_set(REBASELINE, TEXT) | - self._expected_failures.get_test_set(REBASELINE, - IMAGE_PLUS_TEXT)) - - def get_options(self, test): - return self._expected_failures.get_options(test) - - def get_expectations(self, test): - return self._expected_failures.get_expectations(test) - - def get_expectations_string(self, test): - """Returns the expectatons for the given test as an uppercase string. - If there are no expectations for the test, then "PASS" is returned.""" - expectations = self.get_expectations(test) - retval = [] - - for expectation in expectations: - retval.append(self.expectation_to_string(expectation)) - - return " ".join(retval) - - def expectation_to_string(self, expectation): - """Return the uppercased string equivalent of a given expectation.""" - for item in TestExpectationsFile.EXPECTATIONS.items(): - if item[1] == expectation: - return item[0].upper() - raise ValueError(expectation) - - def get_tests_with_result_type(self, result_type): - return self._expected_failures.get_tests_with_result_type(result_type) - - def get_tests_with_timeline(self, timeline): - return self._expected_failures.get_tests_with_timeline(timeline) - - def matches_an_expected_result(self, test, result, - pixel_tests_are_enabled): - expected_results = self._expected_failures.get_expectations(test) - if not pixel_tests_are_enabled: - expected_results = remove_pixel_failures(expected_results) - return result_was_expected(result, expected_results, - self.is_rebaselining(test), self.has_modifier(test, SKIP)) - - def is_rebaselining(self, test): - return self._expected_failures.has_modifier(test, REBASELINE) - - def has_modifier(self, test, modifier): - return self._expected_failures.has_modifier(test, modifier) - - def remove_platform_from_expectations(self, tests, platform): - return self._expected_failures.remove_platform_from_expectations( - tests, platform) - - -def strip_comments(line): - """Strips comments from a line and return None if the line is empty - or else the contents of line with leading and trailing spaces removed - and all other whitespace collapsed""" - - commentIndex = line.find('//') - if commentIndex is -1: - commentIndex = len(line) - - line = re.sub(r'\s+', ' ', line[:commentIndex].strip()) - if line == '': - return None - else: - return line - - -class ModifiersAndExpectations: - """A holder for modifiers and expectations on a test that serializes to - JSON.""" - - def __init__(self, modifiers, expectations): - self.modifiers = modifiers - self.expectations = expectations - - -class ExpectationsJsonEncoder(simplejson.JSONEncoder): - """JSON encoder that can handle ModifiersAndExpectations objects.""" - def default(self, obj): - # A ModifiersAndExpectations object has two fields, each of which - # is a dict. Since JSONEncoders handle all the builtin types directly, - # the only time this routine should be called is on the top level - # object (i.e., the encoder shouldn't recurse). - assert isinstance(obj, ModifiersAndExpectations) - return {"modifiers": obj.modifiers, - "expectations": obj.expectations} - - -class TestExpectationsFile: - """Test expectation files consist of lines with specifications of what - to expect from layout test cases. The test cases can be directories - in which case the expectations apply to all test cases in that - directory and any subdirectory. The format of the file is along the - lines of: - - LayoutTests/fast/js/fixme.js = FAIL - LayoutTests/fast/js/flaky.js = FAIL PASS - LayoutTests/fast/js/crash.js = CRASH TIMEOUT FAIL PASS - ... - - To add other options: - SKIP : LayoutTests/fast/js/no-good.js = TIMEOUT PASS - DEBUG : LayoutTests/fast/js/no-good.js = TIMEOUT PASS - DEBUG SKIP : LayoutTests/fast/js/no-good.js = TIMEOUT PASS - LINUX DEBUG SKIP : LayoutTests/fast/js/no-good.js = TIMEOUT PASS - LINUX WIN : LayoutTests/fast/js/no-good.js = TIMEOUT PASS - - SKIP: Doesn't run the test. - SLOW: The test takes a long time to run, but does not timeout indefinitely. - WONTFIX: For tests that we never intend to pass on a given platform. - DEBUG: Expectations apply only to the debug build. - RELEASE: Expectations apply only to release build. - LINUX/WIN/WIN-XP/WIN-VISTA/WIN-7/MAC: Expectations apply only to these - platforms. - - Notes: - -A test cannot be both SLOW and TIMEOUT - -A test should only be one of IMAGE, TEXT, IMAGE+TEXT, or FAIL. FAIL is - a migratory state that currently means either IMAGE, TEXT, or - IMAGE+TEXT. Once we have finished migrating the expectations, we will - change FAIL to have the meaning of IMAGE+TEXT and remove the IMAGE+TEXT - identifier. - -A test can be included twice, but not via the same path. - -If a test is included twice, then the more precise path wins. - -CRASH tests cannot be WONTFIX - """ - - EXPECTATIONS = {'pass': PASS, - 'fail': FAIL, - 'text': TEXT, - 'image': IMAGE, - 'image+text': IMAGE_PLUS_TEXT, - 'timeout': TIMEOUT, - 'crash': CRASH, - 'missing': MISSING} - - EXPECTATION_DESCRIPTIONS = {SKIP: ('skipped', 'skipped'), - PASS: ('pass', 'passes'), - FAIL: ('failure', 'failures'), - TEXT: ('text diff mismatch', - 'text diff mismatch'), - IMAGE: ('image mismatch', 'image mismatch'), - IMAGE_PLUS_TEXT: ('image and text mismatch', - 'image and text mismatch'), - CRASH: ('DumpRenderTree crash', - 'DumpRenderTree crashes'), - TIMEOUT: ('test timed out', 'tests timed out'), - MISSING: ('no expected result found', - 'no expected results found')} - - EXPECTATION_ORDER = (PASS, CRASH, TIMEOUT, MISSING, IMAGE_PLUS_TEXT, - TEXT, IMAGE, FAIL, SKIP) - - BUILD_TYPES = ('debug', 'release') - - MODIFIERS = {'skip': SKIP, - 'wontfix': WONTFIX, - 'slow': SLOW, - 'rebaseline': REBASELINE, - 'none': NONE} - - TIMELINES = {'wontfix': WONTFIX, - 'now': NOW} - - RESULT_TYPES = {'skip': SKIP, - 'pass': PASS, - 'fail': FAIL, - 'flaky': FLAKY} - - def __init__(self, port, expectations, full_test_list, test_platform_name, - is_debug_mode, is_lint_mode, suppress_errors=False, overrides=None): - """ - expectations: Contents of the expectations file - full_test_list: The list of all tests to be run pending processing of - the expections for those tests. - test_platform_name: name of the platform to match expectations - against. Note that this may be different than - port.test_platform_name() when is_lint_mode is True. - is_debug_mode: Whether we testing a test_shell built debug mode. - is_lint_mode: Whether this is just linting test_expecatations.txt. - suppress_errors: Whether to suppress lint errors. - overrides: test expectations that are allowed to override any - entries in |expectations|. This is used by callers - that need to manage two sets of expectations (e.g., upstream - and downstream expectations). - """ - - self._port = port - self._expectations = expectations - self._full_test_list = full_test_list - self._test_platform_name = test_platform_name - self._is_debug_mode = is_debug_mode - self._is_lint_mode = is_lint_mode - self._overrides = overrides - self._suppress_errors = suppress_errors - self._errors = [] - self._non_fatal_errors = [] - - # Maps relative test paths as listed in the expectations file to a - # list of maps containing modifiers and expectations for each time - # the test is listed in the expectations file. - self._all_expectations = {} - - # Maps a test to its list of expectations. - self._test_to_expectations = {} - - # Maps a test to its list of options (string values) - self._test_to_options = {} - - # Maps a test to its list of modifiers: the constants associated with - # the options minus any bug or platform strings - self._test_to_modifiers = {} - - # Maps a test to the base path that it was listed with in the list. - self._test_list_paths = {} - - self._modifier_to_tests = self._dict_of_sets(self.MODIFIERS) - self._expectation_to_tests = self._dict_of_sets(self.EXPECTATIONS) - self._timeline_to_tests = self._dict_of_sets(self.TIMELINES) - self._result_type_to_tests = self._dict_of_sets(self.RESULT_TYPES) - - self._read(self._get_iterable_expectations(self._expectations), - overrides_allowed=False) - - # List of tests that are in the overrides file (used for checking for - # duplicates inside the overrides file itself). Note that just because - # a test is in this set doesn't mean it's necessarily overridding a - # expectation in the regular expectations; the test might not be - # mentioned in the regular expectations file at all. - self._overridding_tests = set() - - if overrides: - self._read(self._get_iterable_expectations(self._overrides), - overrides_allowed=True) - - self._handle_any_read_errors() - self._process_tests_without_expectations() - - def _handle_any_read_errors(self): - if not self._suppress_errors and ( - len(self._errors) or len(self._non_fatal_errors)): - if self._is_debug_mode: - build_type = 'DEBUG' - else: - build_type = 'RELEASE' - _log.error('') - _log.error("FAILURES FOR PLATFORM: %s, BUILD_TYPE: %s" % - (self._test_platform_name.upper(), build_type)) - - for error in self._non_fatal_errors: - _log.error(error) - _log.error('') - - if len(self._errors): - raise SyntaxError('\n'.join(map(str, self._errors))) - - def _process_tests_without_expectations(self): - expectations = set([PASS]) - options = [] - modifiers = [] - if self._full_test_list: - for test in self._full_test_list: - if not test in self._test_list_paths: - self._add_test(test, modifiers, expectations, options, - overrides_allowed=False) - - def _dict_of_sets(self, strings_to_constants): - """Takes a dict of strings->constants and returns a dict mapping - each constant to an empty set.""" - d = {} - for c in strings_to_constants.values(): - d[c] = set() - return d - - def _get_iterable_expectations(self, expectations_str): - """Returns an object that can be iterated over. Allows for not caring - about whether we're iterating over a file or a new-line separated - string.""" - iterable = [x + "\n" for x in expectations_str.split("\n")] - # Strip final entry if it's empty to avoid added in an extra - # newline. - if iterable[-1] == "\n": - return iterable[:-1] - return iterable - - def get_test_set(self, modifier, expectation=None, include_skips=True): - if expectation is None: - tests = self._modifier_to_tests[modifier] - else: - tests = (self._expectation_to_tests[expectation] & - self._modifier_to_tests[modifier]) - - if not include_skips: - tests = tests - self.get_test_set(SKIP, expectation) - - return tests - - def get_tests_with_result_type(self, result_type): - return self._result_type_to_tests[result_type] - - def get_tests_with_timeline(self, timeline): - return self._timeline_to_tests[timeline] - - def get_options(self, test): - """This returns the entire set of options for the given test - (the modifiers plus the BUGXXXX identifier). This is used by the - LTTF dashboard.""" - return self._test_to_options[test] - - def has_modifier(self, test, modifier): - return test in self._modifier_to_tests[modifier] - - def get_expectations(self, test): - return self._test_to_expectations[test] - - def get_expectations_json_for_all_platforms(self): - # Specify separators in order to get compact encoding. - return ExpectationsJsonEncoder(separators=(',', ':')).encode( - self._all_expectations) - - def get_non_fatal_errors(self): - return self._non_fatal_errors - - def remove_platform_from_expectations(self, tests, platform): - """Returns a copy of the expectations with the tests matching the - platform removed. - - If a test is in the test list and has an option that matches the given - platform, remove the matching platform and save the updated test back - to the file. If no other platforms remaining after removal, delete the - test from the file. - - Args: - tests: list of tests that need to update.. - platform: which platform option to remove. - - Returns: - the updated string. - """ - - assert(platform) - f_orig = self._get_iterable_expectations(self._expectations) - f_new = [] - - tests_removed = 0 - tests_updated = 0 - lineno = 0 - for line in f_orig: - lineno += 1 - action = self._get_platform_update_action(line, lineno, tests, - platform) - assert(action in (NO_CHANGE, REMOVE_TEST, REMOVE_PLATFORM, - ADD_PLATFORMS_EXCEPT_THIS)) - if action == NO_CHANGE: - # Save the original line back to the file - _log.debug('No change to test: %s', line) - f_new.append(line) - elif action == REMOVE_TEST: - tests_removed += 1 - _log.info('Test removed: %s', line) - elif action == REMOVE_PLATFORM: - parts = line.split(':') - new_options = parts[0].replace(platform.upper() + ' ', '', 1) - new_line = ('%s:%s' % (new_options, parts[1])) - f_new.append(new_line) - tests_updated += 1 - _log.info('Test updated: ') - _log.info(' old: %s', line) - _log.info(' new: %s', new_line) - elif action == ADD_PLATFORMS_EXCEPT_THIS: - parts = line.split(':') - new_options = parts[0] - for p in self._port.test_platform_names(): - p = p.upper() - # This is a temp solution for rebaselining tool. - # Do not add tags WIN-7 and WIN-VISTA to test expectations - # if the original line does not specify the platform - # option. - # TODO(victorw): Remove WIN-VISTA and WIN-7 once we have - # reliable Win 7 and Win Vista buildbots setup. - if not p in (platform.upper(), 'WIN-VISTA', 'WIN-7'): - new_options += p + ' ' - new_line = ('%s:%s' % (new_options, parts[1])) - f_new.append(new_line) - tests_updated += 1 - _log.info('Test updated: ') - _log.info(' old: %s', line) - _log.info(' new: %s', new_line) - - _log.info('Total tests removed: %d', tests_removed) - _log.info('Total tests updated: %d', tests_updated) - - return "".join(f_new) - - def parse_expectations_line(self, line, lineno): - """Parses a line from test_expectations.txt and returns a tuple - with the test path, options as a list, expectations as a list.""" - line = strip_comments(line) - if not line: - return (None, None, None) - - options = [] - if line.find(":") is -1: - test_and_expectation = line.split("=") - else: - parts = line.split(":") - options = self._get_options_list(parts[0]) - test_and_expectation = parts[1].split('=') - - test = test_and_expectation[0].strip() - if (len(test_and_expectation) is not 2): - self._add_error(lineno, "Missing expectations.", - test_and_expectation) - expectations = None - else: - expectations = self._get_options_list(test_and_expectation[1]) - - return (test, options, expectations) - - def _get_platform_update_action(self, line, lineno, tests, platform): - """Check the platform option and return the action needs to be taken. - - Args: - line: current line in test expectations file. - lineno: current line number of line - tests: list of tests that need to update.. - platform: which platform option to remove. - - Returns: - NO_CHANGE: no change to the line (comments, test not in the list etc) - REMOVE_TEST: remove the test from file. - REMOVE_PLATFORM: remove this platform option from the test. - ADD_PLATFORMS_EXCEPT_THIS: add all the platforms except this one. - """ - test, options, expectations = self.parse_expectations_line(line, - lineno) - if not test or test not in tests: - return NO_CHANGE - - has_any_platform = False - for option in options: - if option in self._port.test_platform_names(): - has_any_platform = True - if not option == platform: - return REMOVE_PLATFORM - - # If there is no platform specified, then it means apply to all - # platforms. Return the action to add all the platforms except this - # one. - if not has_any_platform: - return ADD_PLATFORMS_EXCEPT_THIS - - return REMOVE_TEST - - def _has_valid_modifiers_for_current_platform(self, options, lineno, - test_and_expectations, modifiers): - """Returns true if the current platform is in the options list or if - no platforms are listed and if there are no fatal errors in the - options list. - - Args: - options: List of lowercase options. - lineno: The line in the file where the test is listed. - test_and_expectations: The path and expectations for the test. - modifiers: The set to populate with modifiers. - """ - has_any_platform = False - has_bug_id = False - for option in options: - if option in self.MODIFIERS: - modifiers.add(option) - elif option in self._port.test_platform_names(): - has_any_platform = True - elif option.startswith('bug'): - has_bug_id = True - elif option not in self.BUILD_TYPES: - self._add_error(lineno, 'Invalid modifier for test: %s' % - option, test_and_expectations) - - if has_any_platform and not self._match_platform(options): - return False - - if not has_bug_id and 'wontfix' not in options: - # TODO(ojan): Turn this into an AddError call once all the - # tests have BUG identifiers. - self._log_non_fatal_error(lineno, 'Test lacks BUG modifier.', - test_and_expectations) - - if 'release' in options or 'debug' in options: - if self._is_debug_mode and 'debug' not in options: - return False - if not self._is_debug_mode and 'release' not in options: - return False - - if self._is_lint_mode and 'rebaseline' in options: - self._add_error(lineno, - 'REBASELINE should only be used for running rebaseline.py. ' - 'Cannot be checked in.', test_and_expectations) - - return True - - def _match_platform(self, options): - """Match the list of options against our specified platform. If any - of the options prefix-match self._platform, return True. This handles - the case where a test is marked WIN and the platform is WIN-VISTA. - - Args: - options: list of options - """ - for opt in options: - if self._test_platform_name.startswith(opt): - return True - return False - - def _add_to_all_expectations(self, test, options, expectations): - # Make all paths unix-style so the dashboard doesn't need to. - test = test.replace('\\', '/') - if not test in self._all_expectations: - self._all_expectations[test] = [] - self._all_expectations[test].append( - ModifiersAndExpectations(options, expectations)) - - def _read(self, expectations, overrides_allowed): - """For each test in an expectations iterable, generate the - expectations for it.""" - lineno = 0 - for line in expectations: - lineno += 1 - - test_list_path, options, expectations = \ - self.parse_expectations_line(line, lineno) - if not expectations: - continue - - self._add_to_all_expectations(test_list_path, - " ".join(options).upper(), - " ".join(expectations).upper()) - - modifiers = set() - if options and not self._has_valid_modifiers_for_current_platform( - options, lineno, test_list_path, modifiers): - continue - - expectations = self._parse_expectations(expectations, lineno, - test_list_path) - - if 'slow' in options and TIMEOUT in expectations: - self._add_error(lineno, - 'A test can not be both slow and timeout. If it times out ' - 'indefinitely, then it should be just timeout.', - test_list_path) - - full_path = os.path.join(self._port.layout_tests_dir(), - test_list_path) - full_path = os.path.normpath(full_path) - # WebKit's way of skipping tests is to add a -disabled suffix. - # So we should consider the path existing if the path or the - # -disabled version exists. - if (not self._port.path_exists(full_path) - and not self._port.path_exists(full_path + '-disabled')): - # Log a non fatal error here since you hit this case any - # time you update test_expectations.txt without syncing - # the LayoutTests directory - self._log_non_fatal_error(lineno, 'Path does not exist.', - test_list_path) - continue - - if not self._full_test_list: - tests = [test_list_path] - else: - tests = self._expand_tests(test_list_path) - - self._add_tests(tests, expectations, test_list_path, lineno, - modifiers, options, overrides_allowed) - - def _get_options_list(self, listString): - return [part.strip().lower() for part in listString.strip().split(' ')] - - def _parse_expectations(self, expectations, lineno, test_list_path): - result = set() - for part in expectations: - if not part in self.EXPECTATIONS: - self._add_error(lineno, 'Unsupported expectation: %s' % part, - test_list_path) - continue - expectation = self.EXPECTATIONS[part] - result.add(expectation) - return result - - def _expand_tests(self, test_list_path): - """Convert the test specification to an absolute, normalized - path and make sure directories end with the OS path separator.""" - path = os.path.join(self._port.layout_tests_dir(), test_list_path) - path = os.path.normpath(path) - if self._port.path_isdir(path): - path = os.path.join(path, '') - - result = [] - for test in self._full_test_list: - if test.startswith(path): - result.append(test) - return result - - def _add_tests(self, tests, expectations, test_list_path, lineno, - modifiers, options, overrides_allowed): - for test in tests: - if self._already_seen_test(test, test_list_path, lineno, - overrides_allowed): - continue - - self._clear_expectations_for_test(test, test_list_path) - self._add_test(test, modifiers, expectations, options, - overrides_allowed) - - def _add_test(self, test, modifiers, expectations, options, - overrides_allowed): - """Sets the expected state for a given test. - - This routine assumes the test has not been added before. If it has, - use _ClearExpectationsForTest() to reset the state prior to - calling this. - - Args: - test: test to add - modifiers: sequence of modifier keywords ('wontfix', 'slow', etc.) - expectations: sequence of expectations (PASS, IMAGE, etc.) - options: sequence of keywords and bug identifiers. - overrides_allowed: whether we're parsing the regular expectations - or the overridding expectations""" - self._test_to_expectations[test] = expectations - for expectation in expectations: - self._expectation_to_tests[expectation].add(test) - - self._test_to_options[test] = options - self._test_to_modifiers[test] = set() - for modifier in modifiers: - mod_value = self.MODIFIERS[modifier] - self._modifier_to_tests[mod_value].add(test) - self._test_to_modifiers[test].add(mod_value) - - if 'wontfix' in modifiers: - self._timeline_to_tests[WONTFIX].add(test) - else: - self._timeline_to_tests[NOW].add(test) - - if 'skip' in modifiers: - self._result_type_to_tests[SKIP].add(test) - elif expectations == set([PASS]): - self._result_type_to_tests[PASS].add(test) - elif len(expectations) > 1: - self._result_type_to_tests[FLAKY].add(test) - else: - self._result_type_to_tests[FAIL].add(test) - - if overrides_allowed: - self._overridding_tests.add(test) - - def _clear_expectations_for_test(self, test, test_list_path): - """Remove prexisting expectations for this test. - This happens if we are seeing a more precise path - than a previous listing. - """ - if test in self._test_list_paths: - self._test_to_expectations.pop(test, '') - self._remove_from_sets(test, self._expectation_to_tests) - self._remove_from_sets(test, self._modifier_to_tests) - self._remove_from_sets(test, self._timeline_to_tests) - self._remove_from_sets(test, self._result_type_to_tests) - - self._test_list_paths[test] = os.path.normpath(test_list_path) - - def _remove_from_sets(self, test, dict): - """Removes the given test from the sets in the dictionary. - - Args: - test: test to look for - dict: dict of sets of files""" - for set_of_tests in dict.itervalues(): - if test in set_of_tests: - set_of_tests.remove(test) - - def _already_seen_test(self, test, test_list_path, lineno, - allow_overrides): - """Returns true if we've already seen a more precise path for this test - than the test_list_path. - """ - if not test in self._test_list_paths: - return False - - prev_base_path = self._test_list_paths[test] - if (prev_base_path == os.path.normpath(test_list_path)): - if (not allow_overrides or test in self._overridding_tests): - if allow_overrides: - expectation_source = "override" - else: - expectation_source = "expectation" - self._add_error(lineno, 'Duplicate %s.' % expectation_source, - test) - return True - else: - # We have seen this path, but that's okay because its - # in the overrides and the earlier path was in the - # expectations. - return False - - # Check if we've already seen a more precise path. - return prev_base_path.startswith(os.path.normpath(test_list_path)) - - def _add_error(self, lineno, msg, path): - """Reports an error that will prevent running the tests. Does not - immediately raise an exception because we'd like to aggregate all the - errors so they can all be printed out.""" - self._errors.append('\nLine:%s %s %s' % (lineno, msg, path)) - - def _log_non_fatal_error(self, lineno, msg, path): - """Reports an error that will not prevent running the tests. These are - still errors, but not bad enough to warrant breaking test running.""" - self._non_fatal_errors.append('Line:%s %s %s' % (lineno, msg, path)) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py deleted file mode 100644 index 55eaf99..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_expectations_unittest.py +++ /dev/null @@ -1,313 +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 test_expectations.py.""" - -import os -import sys -import unittest - -from webkitpy.layout_tests import port -from webkitpy.layout_tests.layout_package.test_expectations import * - -class FunctionsTest(unittest.TestCase): - def test_result_was_expected(self): - # test basics - self.assertEquals(result_was_expected(PASS, set([PASS]), - False, False), True) - self.assertEquals(result_was_expected(TEXT, set([PASS]), - False, False), False) - - # test handling of FAIL expectations - self.assertEquals(result_was_expected(IMAGE_PLUS_TEXT, set([FAIL]), - False, False), True) - self.assertEquals(result_was_expected(IMAGE, set([FAIL]), - False, False), True) - self.assertEquals(result_was_expected(TEXT, set([FAIL]), - False, False), True) - self.assertEquals(result_was_expected(CRASH, set([FAIL]), - False, False), False) - - # test handling of SKIPped tests and results - self.assertEquals(result_was_expected(SKIP, set([CRASH]), - False, True), True) - self.assertEquals(result_was_expected(SKIP, set([CRASH]), - False, False), False) - - # test handling of MISSING results and the REBASELINE modifier - self.assertEquals(result_was_expected(MISSING, set([PASS]), - True, False), True) - self.assertEquals(result_was_expected(MISSING, set([PASS]), - False, False), False) - - def test_remove_pixel_failures(self): - self.assertEquals(remove_pixel_failures(set([TEXT])), - set([TEXT])) - self.assertEquals(remove_pixel_failures(set([PASS])), - set([PASS])) - self.assertEquals(remove_pixel_failures(set([IMAGE])), - set([PASS])) - self.assertEquals(remove_pixel_failures(set([IMAGE_PLUS_TEXT])), - set([TEXT])) - self.assertEquals(remove_pixel_failures(set([PASS, IMAGE, CRASH])), - set([PASS, CRASH])) - - -class Base(unittest.TestCase): - def __init__(self, testFunc, setUp=None, tearDown=None, description=None): - self._port = port.get('test', None) - self._exp = None - unittest.TestCase.__init__(self, testFunc) - - def get_test(self, test_name): - return os.path.join(self._port.layout_tests_dir(), test_name) - - def get_basic_tests(self): - return [self.get_test('failures/expected/text.html'), - self.get_test('failures/expected/image_checksum.html'), - self.get_test('failures/expected/crash.html'), - self.get_test('failures/expected/missing_text.html'), - self.get_test('failures/expected/image.html'), - self.get_test('passes/text.html')] - - def get_basic_expectations(self): - return """ -BUG_TEST : failures/expected/text.html = TEXT -BUG_TEST WONTFIX SKIP : failures/expected/crash.html = CRASH -BUG_TEST REBASELINE : failures/expected/missing_image.html = MISSING -BUG_TEST WONTFIX : failures/expected/image_checksum.html = IMAGE -BUG_TEST WONTFIX WIN : failures/expected/image.html = IMAGE -""" - - def parse_exp(self, expectations, overrides=None, is_lint_mode=False, - is_debug_mode=False): - self._exp = TestExpectations(self._port, - tests=self.get_basic_tests(), - expectations=expectations, - test_platform_name=self._port.test_platform_name(), - is_debug_mode=is_debug_mode, - is_lint_mode=is_lint_mode, - overrides=overrides) - - def assert_exp(self, test, result): - self.assertEquals(self._exp.get_expectations(self.get_test(test)), - set([result])) - - -class TestExpectationsTest(Base): - def test_basic(self): - self.parse_exp(self.get_basic_expectations()) - self.assert_exp('failures/expected/text.html', TEXT) - self.assert_exp('failures/expected/image_checksum.html', IMAGE) - self.assert_exp('passes/text.html', PASS) - self.assert_exp('failures/expected/image.html', PASS) - - def test_multiple_results(self): - self.parse_exp('BUGX : failures/expected/text.html = TEXT CRASH') - self.assertEqual(self._exp.get_expectations( - self.get_test('failures/expected/text.html')), - set([TEXT, CRASH])) - - def test_precedence(self): - # This tests handling precedence of specific lines over directories - # and tests expectations covering entire directories. - exp_str = """ -BUGX : failures/expected/text.html = TEXT -BUGX WONTFIX : failures/expected = IMAGE -""" - self.parse_exp(exp_str) - self.assert_exp('failures/expected/text.html', TEXT) - self.assert_exp('failures/expected/crash.html', IMAGE) - - def test_release_mode(self): - self.parse_exp('BUGX DEBUG : failures/expected/text.html = TEXT', - is_debug_mode=True) - self.assert_exp('failures/expected/text.html', TEXT) - self.parse_exp('BUGX RELEASE : failures/expected/text.html = TEXT', - is_debug_mode=True) - self.assert_exp('failures/expected/text.html', PASS) - self.parse_exp('BUGX DEBUG : failures/expected/text.html = TEXT', - is_debug_mode=False) - self.assert_exp('failures/expected/text.html', PASS) - self.parse_exp('BUGX RELEASE : failures/expected/text.html = TEXT', - is_debug_mode=False) - self.assert_exp('failures/expected/text.html', TEXT) - - def test_get_options(self): - self.parse_exp(self.get_basic_expectations()) - self.assertEqual(self._exp.get_options( - self.get_test('passes/text.html')), []) - - def test_expectations_json_for_all_platforms(self): - self.parse_exp(self.get_basic_expectations()) - json_str = self._exp.get_expectations_json_for_all_platforms() - # FIXME: test actual content? - self.assertTrue(json_str) - - def test_get_expectations_string(self): - self.parse_exp(self.get_basic_expectations()) - self.assertEquals(self._exp.get_expectations_string( - self.get_test('failures/expected/text.html')), - 'TEXT') - - def test_expectation_to_string(self): - # Normal cases are handled by other tests. - self.parse_exp(self.get_basic_expectations()) - self.assertRaises(ValueError, self._exp.expectation_to_string, - -1) - - def test_get_test_set(self): - # Handle some corner cases for this routine not covered by other tests. - self.parse_exp(self.get_basic_expectations()) - s = self._exp._expected_failures.get_test_set(WONTFIX) - self.assertEqual(s, - set([self.get_test('failures/expected/crash.html'), - self.get_test('failures/expected/image_checksum.html')])) - s = self._exp._expected_failures.get_test_set(WONTFIX, CRASH) - self.assertEqual(s, - set([self.get_test('failures/expected/crash.html')])) - s = self._exp._expected_failures.get_test_set(WONTFIX, CRASH, - include_skips=False) - self.assertEqual(s, set([])) - - def test_syntax_missing_expectation(self): - # This is missing the expectation. - self.assertRaises(SyntaxError, self.parse_exp, - 'BUG_TEST: failures/expected/text.html', - is_debug_mode=True) - - def test_syntax_invalid_option(self): - self.assertRaises(SyntaxError, self.parse_exp, - 'BUG_TEST FOO: failures/expected/text.html = PASS') - - def test_syntax_invalid_expectation(self): - # This is missing the expectation. - self.assertRaises(SyntaxError, self.parse_exp, - 'BUG_TEST: failures/expected/text.html = FOO') - - def test_syntax_missing_bugid(self): - # This should log a non-fatal error. - self.parse_exp('SLOW : failures/expected/text.html = TEXT') - self.assertEqual( - len(self._exp._expected_failures.get_non_fatal_errors()), 1) - - def test_semantic_slow_and_timeout(self): - # A test cannot be SLOW and expected to TIMEOUT. - self.assertRaises(SyntaxError, self.parse_exp, - 'BUG_TEST SLOW : failures/expected/timeout.html = TIMEOUT') - - def test_semantic_rebaseline(self): - # Can't lint a file w/ 'REBASELINE' in it. - self.assertRaises(SyntaxError, self.parse_exp, - 'BUG_TEST REBASELINE : failures/expected/text.html = TEXT', - is_lint_mode=True) - - def test_semantic_duplicates(self): - self.assertRaises(SyntaxError, self.parse_exp, """ -BUG_TEST : failures/expected/text.html = TEXT -BUG_TEST : failures/expected/text.html = IMAGE""") - - self.assertRaises(SyntaxError, self.parse_exp, - self.get_basic_expectations(), """ -BUG_TEST : failures/expected/text.html = TEXT -BUG_TEST : failures/expected/text.html = IMAGE""") - - def test_semantic_missing_file(self): - # This should log a non-fatal error. - self.parse_exp('BUG_TEST : missing_file.html = TEXT') - self.assertEqual( - len(self._exp._expected_failures.get_non_fatal_errors()), 1) - - - def test_overrides(self): - self.parse_exp(self.get_basic_expectations(), """ -BUG_OVERRIDE : failures/expected/text.html = IMAGE""") - self.assert_exp('failures/expected/text.html', IMAGE) - - def test_matches_an_expected_result(self): - - def match(test, result, pixel_tests_enabled): - return self._exp.matches_an_expected_result( - self.get_test(test), result, pixel_tests_enabled) - - self.parse_exp(self.get_basic_expectations()) - self.assertTrue(match('failures/expected/text.html', TEXT, True)) - self.assertTrue(match('failures/expected/text.html', TEXT, False)) - self.assertFalse(match('failures/expected/text.html', CRASH, True)) - self.assertFalse(match('failures/expected/text.html', CRASH, False)) - self.assertTrue(match('failures/expected/image_checksum.html', IMAGE, - True)) - self.assertTrue(match('failures/expected/image_checksum.html', PASS, - False)) - self.assertTrue(match('failures/expected/crash.html', SKIP, False)) - self.assertTrue(match('passes/text.html', PASS, False)) - - -class RebaseliningTest(Base): - """Test rebaselining-specific functionality.""" - def assertRemove(self, platform, input_expectations, expected_expectations): - self.parse_exp(input_expectations) - test = self.get_test('failures/expected/text.html') - actual_expectations = self._exp.remove_platform_from_expectations( - test, platform) - self.assertEqual(expected_expectations, actual_expectations) - - def test_no_get_rebaselining_failures(self): - self.parse_exp(self.get_basic_expectations()) - self.assertEqual(len(self._exp.get_rebaselining_failures()), 0) - - def test_get_rebaselining_failures_expand(self): - self.parse_exp(""" -BUG_TEST REBASELINE : failures/expected/text.html = TEXT -""") - self.assertEqual(len(self._exp.get_rebaselining_failures()), 1) - - def test_remove_expand(self): - self.assertRemove('mac', - 'BUGX REBASELINE : failures/expected/text.html = TEXT\n', - 'BUGX REBASELINE WIN : failures/expected/text.html = TEXT\n') - - def test_remove_mac_win(self): - self.assertRemove('mac', - 'BUGX REBASELINE MAC WIN : failures/expected/text.html = TEXT\n', - 'BUGX REBASELINE WIN : failures/expected/text.html = TEXT\n') - - def test_remove_mac_mac(self): - self.assertRemove('mac', - 'BUGX REBASELINE MAC : failures/expected/text.html = TEXT\n', - '') - - def test_remove_nothing(self): - self.assertRemove('mac', - '\n\n', - '\n\n') - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py deleted file mode 100644 index 6d55761..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures.py +++ /dev/null @@ -1,282 +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. - -"""Classes for failures that occur during tests.""" - -import os -import test_expectations - -import cPickle - - -def determine_result_type(failure_list): - """Takes a set of test_failures and returns which result type best fits - the list of failures. "Best fits" means we use the worst type of failure. - - Returns: - one of the test_expectations result types - PASS, TEXT, CRASH, etc.""" - - if not failure_list or len(failure_list) == 0: - return test_expectations.PASS - - failure_types = [type(f) for f in failure_list] - if FailureCrash in failure_types: - return test_expectations.CRASH - elif FailureTimeout in failure_types: - return test_expectations.TIMEOUT - elif (FailureMissingResult in failure_types or - FailureMissingImage in failure_types or - FailureMissingImageHash in failure_types): - return test_expectations.MISSING - else: - is_text_failure = FailureTextMismatch in failure_types - is_image_failure = (FailureImageHashIncorrect in failure_types or - FailureImageHashMismatch in failure_types) - if is_text_failure and is_image_failure: - return test_expectations.IMAGE_PLUS_TEXT - elif is_text_failure: - return test_expectations.TEXT - elif is_image_failure: - return test_expectations.IMAGE - else: - raise ValueError("unclassifiable set of failures: " - + str(failure_types)) - - -class TestFailure(object): - """Abstract base class that defines the failure interface.""" - - @staticmethod - def loads(s): - """Creates a TestFailure object from the specified string.""" - return cPickle.loads(s) - - @staticmethod - def message(): - """Returns a string describing the failure in more detail.""" - raise NotImplementedError - - def __eq__(self, other): - return self.__class__.__name__ == other.__class__.__name__ - - def __ne__(self, other): - return self.__class__.__name__ != other.__class__.__name__ - - def dumps(self): - """Returns the string/JSON representation of a TestFailure.""" - return cPickle.dumps(self) - - def result_html_output(self, filename): - """Returns an HTML string to be included on the results.html page.""" - raise NotImplementedError - - def should_kill_dump_render_tree(self): - """Returns True if we should kill DumpRenderTree before the next - test.""" - return False - - def relative_output_filename(self, filename, modifier): - """Returns a relative filename inside the output dir that contains - modifier. - - For example, if filename is fast\dom\foo.html and modifier is - "-expected.txt", the return value is fast\dom\foo-expected.txt - - Args: - filename: relative filename to test file - modifier: a string to replace the extension of filename with - - Return: - The relative windows path to the output filename - """ - return os.path.splitext(filename)[0] + modifier - - -class FailureWithType(TestFailure): - """Base class that produces standard HTML output based on the test type. - - Subclasses may commonly choose to override the ResultHtmlOutput, but still - use the standard OutputLinks. - """ - - def __init__(self): - TestFailure.__init__(self) - - # Filename suffixes used by ResultHtmlOutput. - OUT_FILENAMES = () - - def output_links(self, filename, out_names): - """Returns a string holding all applicable output file links. - - Args: - filename: the test filename, used to construct the result file names - out_names: list of filename suffixes for the files. If three or more - suffixes are in the list, they should be [actual, expected, diff, - wdiff]. Two suffixes should be [actual, expected], and a - single item is the [actual] filename suffix. - If out_names is empty, returns the empty string. - """ - # FIXME: Seems like a bad idea to separate the display name data - # from the path data by hard-coding the display name here - # and passing in the path information via out_names. - # - # FIXME: Also, we don't know for sure that these files exist, - # and we shouldn't be creating links to files that don't exist - # (for example, if we don't actually have wdiff output). - links = [''] - uris = [self.relative_output_filename(filename, fn) for - fn in out_names] - if len(uris) > 1: - links.append("<a href='%s'>expected</a>" % uris[1]) - if len(uris) > 0: - links.append("<a href='%s'>actual</a>" % uris[0]) - if len(uris) > 2: - links.append("<a href='%s'>diff</a>" % uris[2]) - if len(uris) > 3: - links.append("<a href='%s'>wdiff</a>" % uris[3]) - if len(uris) > 4: - links.append("<a href='%s'>pretty diff</a>" % uris[4]) - return ' '.join(links) - - def result_html_output(self, filename): - return self.message() + self.output_links(filename, self.OUT_FILENAMES) - - -class FailureTimeout(TestFailure): - """Test timed out. We also want to restart DumpRenderTree if this - happens.""" - - @staticmethod - def message(): - return "Test timed out" - - def result_html_output(self, filename): - return "<strong>%s</strong>" % self.message() - - def should_kill_dump_render_tree(self): - return True - - -class FailureCrash(TestFailure): - """Test shell crashed.""" - - @staticmethod - def message(): - return "Test shell crashed" - - def result_html_output(self, filename): - # FIXME: create a link to the minidump file - stack = self.relative_output_filename(filename, "-stack.txt") - return "<strong>%s</strong> <a href=%s>stack</a>" % (self.message(), - stack) - - def should_kill_dump_render_tree(self): - return True - - -class FailureMissingResult(FailureWithType): - """Expected result was missing.""" - OUT_FILENAMES = ("-actual.txt",) - - @staticmethod - def message(): - return "No expected results found" - - def result_html_output(self, filename): - return ("<strong>%s</strong>" % self.message() + - self.output_links(filename, self.OUT_FILENAMES)) - - -class FailureTextMismatch(FailureWithType): - """Text diff output failed.""" - # Filename suffixes used by ResultHtmlOutput. - # FIXME: Why don't we use the constants from TestTypeBase here? - OUT_FILENAMES = ("-actual.txt", "-expected.txt", "-diff.txt", - "-wdiff.html", "-pretty-diff.html") - - @staticmethod - def message(): - return "Text diff mismatch" - - -class FailureMissingImageHash(FailureWithType): - """Actual result hash was missing.""" - # Chrome doesn't know to display a .checksum file as text, so don't bother - # putting in a link to the actual result. - - @staticmethod - def message(): - return "No expected image hash found" - - def result_html_output(self, filename): - return "<strong>%s</strong>" % self.message() - - -class FailureMissingImage(FailureWithType): - """Actual result image was missing.""" - OUT_FILENAMES = ("-actual.png",) - - @staticmethod - def message(): - return "No expected image found" - - def result_html_output(self, filename): - return ("<strong>%s</strong>" % self.message() + - self.output_links(filename, self.OUT_FILENAMES)) - - -class FailureImageHashMismatch(FailureWithType): - """Image hashes didn't match.""" - OUT_FILENAMES = ("-actual.png", "-expected.png", "-diff.png") - - @staticmethod - def message(): - # We call this a simple image mismatch to avoid confusion, since - # we link to the PNGs rather than the checksums. - return "Image mismatch" - - -class FailureImageHashIncorrect(FailureWithType): - """Actual result hash is incorrect.""" - # Chrome doesn't know to display a .checksum file as text, so don't bother - # putting in a link to the actual result. - - @staticmethod - def message(): - return "Images match, expected image hash incorrect. " - - def result_html_output(self, filename): - return "<strong>%s</strong>" % self.message() - -# Convenient collection of all failure classes for anything that might -# need to enumerate over them all. -ALL_FAILURE_CLASSES = (FailureTimeout, FailureCrash, FailureMissingResult, - FailureTextMismatch, FailureMissingImageHash, - FailureMissingImage, FailureImageHashMismatch, - FailureImageHashIncorrect) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures_unittest.py deleted file mode 100644 index 3e3528d..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_failures_unittest.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. - -""""Tests code paths not covered by the regular unit tests.""" - -import unittest - -from webkitpy.layout_tests.layout_package.test_failures import * - - -class Test(unittest.TestCase): - def assertResultHtml(self, failure_obj): - self.assertNotEqual(failure_obj.result_html_output('foo'), None) - - def assert_loads(self, cls): - failure_obj = cls() - s = failure_obj.dumps() - new_failure_obj = TestFailure.loads(s) - self.assertTrue(isinstance(new_failure_obj, cls)) - - self.assertEqual(failure_obj, new_failure_obj) - - # Also test that != is implemented. - self.assertFalse(failure_obj != new_failure_obj) - - def test_crash(self): - self.assertResultHtml(FailureCrash()) - - def test_hash_incorrect(self): - self.assertResultHtml(FailureImageHashIncorrect()) - - def test_missing(self): - self.assertResultHtml(FailureMissingResult()) - - def test_missing_image(self): - self.assertResultHtml(FailureMissingImage()) - - def test_missing_image_hash(self): - self.assertResultHtml(FailureMissingImageHash()) - - def test_timeout(self): - self.assertResultHtml(FailureTimeout()) - - def test_unknown_failure_type(self): - class UnknownFailure(TestFailure): - pass - - failure_obj = UnknownFailure() - self.assertRaises(ValueError, determine_result_type, [failure_obj]) - self.assertRaises(NotImplementedError, failure_obj.message) - self.assertRaises(NotImplementedError, failure_obj.result_html_output, - "foo.txt") - - def test_loads(self): - for c in ALL_FAILURE_CLASSES: - self.assert_loads(c) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_output.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_output.py deleted file mode 100644 index e809be6..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_output.py +++ /dev/null @@ -1,56 +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 TestOutput(object): - """Groups information about a test output for easy passing of data. - - This is used not only for a actual test output, but also for grouping - expected test output. - """ - - def __init__(self, text, image, image_hash, - crash=None, test_time=None, timeout=None, error=None): - """Initializes a TestOutput object. - - Args: - text: a text output - image: an image output - image_hash: a string containing the checksum of the image - crash: a boolean indicating whether the driver crashed on the test - test_time: a time which the test has taken - timeout: a boolean indicating whehter the test timed out - error: any unexpected or additional (or error) text output - """ - self.text = text - self.image = image - self.image_hash = image_hash - self.crash = crash - self.test_time = test_time - self.timeout = timeout - self.error = error diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results.py deleted file mode 100644 index 2417fb7..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results.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 cPickle - -import test_failures - - -class TestResult(object): - """Data object containing the results of a single test.""" - - @staticmethod - def loads(str): - return cPickle.loads(str) - - def __init__(self, filename, failures, test_run_time, - total_time_for_all_diffs, time_for_diffs): - self.failures = failures - self.filename = filename - self.test_run_time = test_run_time - self.time_for_diffs = time_for_diffs - self.total_time_for_all_diffs = total_time_for_all_diffs - self.type = test_failures.determine_result_type(failures) - - def __eq__(self, other): - return (self.filename == other.filename and - self.failures == other.failures and - self.test_run_time == other.test_run_time and - self.time_for_diffs == other.time_for_diffs and - self.total_time_for_all_diffs == other.total_time_for_all_diffs) - - def __ne__(self, other): - return not (self == other) - - def dumps(self): - return cPickle.dumps(self) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results_unittest.py deleted file mode 100644 index 5921666..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results_unittest.py +++ /dev/null @@ -1,52 +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 test_results import TestResult - - -class Test(unittest.TestCase): - def test_loads(self): - result = TestResult(filename='foo', - failures=[], - test_run_time=1.1, - total_time_for_all_diffs=0.5, - time_for_diffs=0.5) - s = result.dumps() - new_result = TestResult.loads(s) - self.assertTrue(isinstance(new_result, TestResult)) - - self.assertEqual(new_result, result) - - # Also check that != is implemented. - self.assertFalse(new_result != result) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results_uploader.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results_uploader.py deleted file mode 100644 index 033c8c6..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/test_results_uploader.py +++ /dev/null @@ -1,107 +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. - -from __future__ import with_statement - -import codecs -import mimetypes -import socket -import urllib2 - -from webkitpy.common.net.networktransaction import NetworkTransaction - -def get_mime_type(filename): - return mimetypes.guess_type(filename)[0] or 'application/octet-stream' - - -def _encode_multipart_form_data(fields, files): - """Encode form fields for multipart/form-data. - - Args: - fields: A sequence of (name, value) elements for regular form fields. - files: A sequence of (name, filename, value) elements for data to be - uploaded as files. - Returns: - (content_type, body) ready for httplib.HTTP instance. - - Source: - http://code.google.com/p/rietveld/source/browse/trunk/upload.py - """ - BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-' - CRLF = '\r\n' - lines = [] - - for key, value in fields: - lines.append('--' + BOUNDARY) - lines.append('Content-Disposition: form-data; name="%s"' % key) - lines.append('') - if isinstance(value, unicode): - value = value.encode('utf-8') - lines.append(value) - - for key, filename, value in files: - lines.append('--' + BOUNDARY) - lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) - lines.append('Content-Type: %s' % get_mime_type(filename)) - lines.append('') - if isinstance(value, unicode): - value = value.encode('utf-8') - lines.append(value) - - lines.append('--' + BOUNDARY + '--') - lines.append('') - body = CRLF.join(lines) - content_type = 'multipart/form-data; boundary=%s' % BOUNDARY - return content_type, body - - -class TestResultsUploader: - def __init__(self, host): - self._host = host - - def _upload_files(self, attrs, file_objs): - url = "http://%s/testfile/upload" % self._host - content_type, data = _encode_multipart_form_data(attrs, file_objs) - headers = {"Content-Type": content_type} - request = urllib2.Request(url, data, headers) - urllib2.urlopen(request) - - def upload(self, params, files, timeout_seconds): - file_objs = [] - for filename, path in files: - with codecs.open(path, "rb") as file: - file_objs.append(('file', filename, file.read())) - - orig_timeout = socket.getdefaulttimeout() - try: - socket.setdefaulttimeout(timeout_seconds) - NetworkTransaction(timeout_seconds=timeout_seconds).run( - lambda: self._upload_files(params, file_objs)) - finally: - socket.setdefaulttimeout(orig_timeout) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/__init__.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/__init__.py deleted file mode 100644 index e3ad6f4..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/__init__.py +++ /dev/null @@ -1,32 +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. - -"""Port-specific entrypoints for the layout tests test infrastructure.""" - -from factory import get diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/apache_http_server.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/apache_http_server.py deleted file mode 100644 index 46617f6..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/apache_http_server.py +++ /dev/null @@ -1,230 +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. - -"""A class to start/stop the apache http server used by layout tests.""" - - -from __future__ import with_statement - -import codecs -import logging -import optparse -import os -import re -import subprocess -import sys - -import http_server_base - -_log = logging.getLogger("webkitpy.layout_tests.port.apache_http_server") - - -class LayoutTestApacheHttpd(http_server_base.HttpServerBase): - - def __init__(self, port_obj, output_dir): - """Args: - port_obj: handle to the platform-specific routines - output_dir: the absolute path to the layout test result directory - """ - http_server_base.HttpServerBase.__init__(self, port_obj) - self._output_dir = output_dir - self._httpd_proc = None - port_obj.maybe_make_directory(output_dir) - - self.mappings = [{'port': 8000}, - {'port': 8080}, - {'port': 8081}, - {'port': 8443, 'sslcert': True}] - - # The upstream .conf file assumed the existence of /tmp/WebKit for - # placing apache files like the lock file there. - self._runtime_path = os.path.join("/tmp", "WebKit") - port_obj.maybe_make_directory(self._runtime_path) - - # The PID returned when Apache is started goes away (due to dropping - # privileges?). The proper controlling PID is written to a file in the - # apache runtime directory. - self._pid_file = os.path.join(self._runtime_path, 'httpd.pid') - - test_dir = self._port_obj.layout_tests_dir() - js_test_resources_dir = self._cygwin_safe_join(test_dir, "fast", "js", - "resources") - mime_types_path = self._cygwin_safe_join(test_dir, "http", "conf", - "mime.types") - cert_file = self._cygwin_safe_join(test_dir, "http", "conf", - "webkit-httpd.pem") - access_log = self._cygwin_safe_join(output_dir, "access_log.txt") - error_log = self._cygwin_safe_join(output_dir, "error_log.txt") - document_root = self._cygwin_safe_join(test_dir, "http", "tests") - - # FIXME: We shouldn't be calling a protected method of _port_obj! - executable = self._port_obj._path_to_apache() - if self._is_cygwin(): - executable = self._get_cygwin_path(executable) - - cmd = [executable, - '-f', "\"%s\"" % self._get_apache_config_file_path(test_dir, output_dir), - '-C', "\'DocumentRoot \"%s\"\'" % document_root, - '-c', "\'Alias /js-test-resources \"%s\"'" % js_test_resources_dir, - '-C', "\'Listen %s\'" % "127.0.0.1:8000", - '-C', "\'Listen %s\'" % "127.0.0.1:8081", - '-c', "\'TypesConfig \"%s\"\'" % mime_types_path, - '-c', "\'CustomLog \"%s\" common\'" % access_log, - '-c', "\'ErrorLog \"%s\"\'" % error_log, - '-C', "\'User \"%s\"\'" % os.environ.get("USERNAME", - os.environ.get("USER", ""))] - - if self._is_cygwin(): - cygbin = self._port_obj._path_from_base('third_party', 'cygwin', - 'bin') - # Not entirely sure why, but from cygwin we need to run the - # httpd command through bash. - self._start_cmd = [ - os.path.join(cygbin, 'bash.exe'), - '-c', - 'PATH=%s %s' % (self._get_cygwin_path(cygbin), " ".join(cmd)), - ] - else: - # TODO(ojan): When we get cygwin using Apache 2, use set the - # cert file for cygwin as well. - cmd.extend(['-c', "\'SSLCertificateFile %s\'" % cert_file]) - # Join the string here so that Cygwin/Windows and Mac/Linux - # can use the same code. Otherwise, we could remove the single - # quotes above and keep cmd as a sequence. - self._start_cmd = " ".join(cmd) - - def _is_cygwin(self): - return sys.platform in ("win32", "cygwin") - - def _cygwin_safe_join(self, *parts): - """Returns a platform appropriate path.""" - path = os.path.join(*parts) - if self._is_cygwin(): - return self._get_cygwin_path(path) - return path - - def _get_cygwin_path(self, path): - """Convert a Windows path to a cygwin path. - - The cygpath utility insists on converting paths that it thinks are - Cygwin root paths to what it thinks the correct roots are. So paths - such as "C:\b\slave\webkit-release\build\third_party\cygwin\bin" - are converted to plain "/usr/bin". To avoid this, we - do the conversion manually. - - The path is expected to be an absolute path, on any drive. - """ - drive_regexp = re.compile(r'([a-z]):[/\\]', re.IGNORECASE) - - def lower_drive(matchobj): - return '/cygdrive/%s/' % matchobj.group(1).lower() - path = drive_regexp.sub(lower_drive, path) - return path.replace('\\', '/') - - def _get_apache_config_file_path(self, test_dir, output_dir): - """Returns the path to the apache config file to use. - Args: - test_dir: absolute path to the LayoutTests directory. - output_dir: absolute path to the layout test results directory. - """ - httpd_config = self._port_obj._path_to_apache_config_file() - httpd_config_copy = os.path.join(output_dir, "httpd.conf") - # httpd.conf is always utf-8 according to http://archive.apache.org/gnats/10125 - with codecs.open(httpd_config, "r", "utf-8") as httpd_config_file: - httpd_conf = httpd_config_file.read() - if self._is_cygwin(): - # This is a gross hack, but it lets us use the upstream .conf file - # and our checked in cygwin. This tells the server the root - # directory to look in for .so modules. It will use this path - # plus the relative paths to the .so files listed in the .conf - # file. We have apache/cygwin checked into our tree so - # people don't have to install it into their cygwin. - cygusr = self._port_obj._path_from_base('third_party', 'cygwin', - 'usr') - httpd_conf = httpd_conf.replace('ServerRoot "/usr"', - 'ServerRoot "%s"' % self._get_cygwin_path(cygusr)) - - with codecs.open(httpd_config_copy, "w", "utf-8") as file: - file.write(httpd_conf) - - if self._is_cygwin(): - return self._get_cygwin_path(httpd_config_copy) - return httpd_config_copy - - def _get_virtual_host_config(self, document_root, port, ssl=False): - """Returns a <VirtualHost> directive block for an httpd.conf file. - It will listen to 127.0.0.1 on each of the given port. - """ - return '\n'.join(('<VirtualHost 127.0.0.1:%s>' % port, - 'DocumentRoot "%s"' % document_root, - ssl and 'SSLEngine On' or '', - '</VirtualHost>', '')) - - def _start_httpd_process(self): - """Starts the httpd process and returns whether there were errors.""" - # Use shell=True because we join the arguments into a string for - # the sake of Window/Cygwin and it needs quoting that breaks - # shell=False. - # FIXME: We should not need to be joining shell arguments into strings. - # shell=True is a trail of tears. - # Note: Not thread safe: http://bugs.python.org/issue2320 - self._httpd_proc = subprocess.Popen(self._start_cmd, - stderr=subprocess.PIPE, - shell=True) - err = self._httpd_proc.stderr.read() - if len(err): - _log.debug(err) - return False - return True - - def start(self): - """Starts the apache http server.""" - # Stop any currently running servers. - self.stop() - - _log.debug("Starting apache http server") - server_started = self.wait_for_action(self._start_httpd_process) - if server_started: - _log.debug("Apache started. Testing ports") - server_started = self.wait_for_action( - self.is_server_running_on_all_ports) - - if server_started: - _log.debug("Server successfully started") - else: - raise Exception('Failed to start http server') - - def stop(self): - """Stops the apache http server.""" - _log.debug("Shutting down any running http servers") - httpd_pid = None - if os.path.exists(self._pid_file): - httpd_pid = int(open(self._pid_file).readline()) - # FIXME: We shouldn't be calling a protected method of _port_obj! - self._port_obj._shut_down_http_server(httpd_pid) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py deleted file mode 100644 index 757318d..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py +++ /dev/null @@ -1,861 +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 Google name 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. - -"""Abstract base class of Port-specific entrypoints for the layout tests -test infrastructure (the Port and Driver classes).""" - -import cgi -import difflib -import errno -import os -import shlex -import sys -import time - -import apache_http_server -import config as port_config -import http_lock -import http_server -import test_files -import websocket_server - -from webkitpy.common import system -from webkitpy.common.system import filesystem -from webkitpy.common.system import logutils -from webkitpy.common.system import path -from webkitpy.common.system.executive import Executive, ScriptError -from webkitpy.common.system.user import User - - -_log = logutils.get_logger(__file__) - - -class DummyOptions(object): - """Fake implementation of optparse.Values. Cloned from - webkitpy.tool.mocktool.MockOptions. - - """ - - def __init__(self, **kwargs): - # The caller can set option values using keyword arguments. We don't - # set any values by default because we don't know how this - # object will be used. Generally speaking unit tests should - # subclass this or provider wrapper functions that set a common - # set of options. - for key, value in kwargs.items(): - self.__dict__[key] = value - - -# FIXME: This class should merge with webkitpy.webkit_port at some point. -class Port(object): - """Abstract class for Port-specific hooks for the layout_test package.""" - - def __init__(self, port_name=None, options=None, - executive=None, - user=None, - filesystem=None, - config=None, - **kwargs): - self._name = port_name - self._options = options - if self._options is None: - # FIXME: Ideally we'd have a package-wide way to get a - # well-formed options object that had all of the necessary - # options defined on it. - self._options = DummyOptions() - self._executive = executive or Executive() - self._user = user or User() - self._filesystem = filesystem or system.filesystem.FileSystem() - self._config = config or port_config.Config(self._executive, - self._filesystem) - self._helper = None - self._http_server = None - self._webkit_base_dir = None - self._websocket_server = None - self._http_lock = None - - # Python's Popen has a bug that causes any pipes opened to a - # process that can't be executed to be leaked. Since this - # code is specifically designed to tolerate exec failures - # to gracefully handle cases where wdiff is not installed, - # the bug results in a massive file descriptor leak. As a - # workaround, if an exec failure is ever experienced for - # wdiff, assume it's not available. This will leak one - # file descriptor but that's better than leaking each time - # wdiff would be run. - # - # http://mail.python.org/pipermail/python-list/ - # 2008-August/505753.html - # http://bugs.python.org/issue3210 - self._wdiff_available = True - - self._pretty_patch_path = self.path_from_webkit_base("BugsSite", - "PrettyPatch", "prettify.rb") - self._pretty_patch_available = True - self.set_option_default('configuration', None) - if self._options.configuration is None: - self._options.configuration = self.default_configuration() - - def default_child_processes(self): - """Return the number of DumpRenderTree instances to use for this - port.""" - return self._executive.cpu_count() - - def baseline_path(self): - """Return the absolute path to the directory to store new baselines - in for this port.""" - raise NotImplementedError('Port.baseline_path') - - def baseline_search_path(self): - """Return a list of absolute paths to directories to search under for - baselines. The directories are searched in order.""" - raise NotImplementedError('Port.baseline_search_path') - - def check_build(self, needs_http): - """This routine is used to ensure that the build is up to date - and all the needed binaries are present.""" - raise NotImplementedError('Port.check_build') - - def check_sys_deps(self, needs_http): - """If the port needs to do some runtime checks to ensure that the - tests can be run successfully, it should override this routine. - This step can be skipped with --nocheck-sys-deps. - - Returns whether the system is properly configured.""" - return True - - def check_image_diff(self, override_step=None, logging=True): - """This routine is used to check whether image_diff binary exists.""" - raise NotImplementedError('Port.check_image_diff') - - def check_pretty_patch(self): - """Checks whether we can use the PrettyPatch ruby script.""" - - # check if Ruby is installed - try: - result = self._executive.run_command(['ruby', '--version']) - except OSError, e: - if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]: - _log.error("Ruby is not installed; " - "can't generate pretty patches.") - _log.error('') - return False - - if not self.path_exists(self._pretty_patch_path): - _log.error('Unable to find %s .' % self._pretty_patch_path) - _log.error("Can't generate pretty patches.") - _log.error('') - return False - - return True - - def compare_text(self, expected_text, actual_text): - """Return whether or not the two strings are *not* equal. This - routine is used to diff text output. - - While this is a generic routine, we include it in the Port - interface so that it can be overriden for testing purposes.""" - return expected_text != actual_text - - def diff_image(self, expected_contents, actual_contents, - diff_filename=None, tolerance=0): - """Compare two images and produce a delta image file. - - Return True if the two images are different, False if they are the same. - Also produce a delta image of the two images and write that into - |diff_filename| if it is not None. - - |tolerance| should be a percentage value (0.0 - 100.0). - If it is omitted, the port default tolerance value is used. - - """ - raise NotImplementedError('Port.diff_image') - - - def diff_text(self, expected_text, actual_text, - expected_filename, actual_filename): - """Returns a string containing the diff of the two text strings - in 'unified diff' format. - - While this is a generic routine, we include it in the Port - interface so that it can be overriden for testing purposes.""" - - # The filenames show up in the diff output, make sure they're - # raw bytes and not unicode, so that they don't trigger join() - # trying to decode the input. - def to_raw_bytes(str): - if isinstance(str, unicode): - return str.encode('utf-8') - return str - expected_filename = to_raw_bytes(expected_filename) - actual_filename = to_raw_bytes(actual_filename) - diff = difflib.unified_diff(expected_text.splitlines(True), - actual_text.splitlines(True), - expected_filename, - actual_filename) - return ''.join(diff) - - def driver_name(self): - """Returns the name of the actual binary that is performing the test, - so that it can be referred to in log messages. In most cases this - will be DumpRenderTree, but if a port uses a binary with a different - name, it can be overridden here.""" - return "DumpRenderTree" - - def expected_baselines(self, filename, suffix, all_baselines=False): - """Given a test name, finds where the baseline results are located. - - Args: - filename: absolute filename to test file - suffix: file suffix of the expected results, including dot; e.g. - '.txt' or '.png'. This should not be None, but may be an empty - string. - all_baselines: If True, return an ordered list of all baseline paths - for the given platform. If False, return only the first one. - Returns - a list of ( platform_dir, results_filename ), where - platform_dir - abs path to the top of the results tree (or test - tree) - results_filename - relative path from top of tree to the results - file - (os.path.join of the two gives you the full path to the file, - unless None was returned.) - Return values will be in the format appropriate for the current - platform (e.g., "\\" for path separators on Windows). If the results - file is not found, then None will be returned for the directory, - but the expected relative pathname will still be returned. - - This routine is generic but lives here since it is used in - conjunction with the other baseline and filename routines that are - platform specific. - """ - testname = os.path.splitext(self.relative_test_filename(filename))[0] - - baseline_filename = testname + '-expected' + suffix - - baseline_search_path = self.baseline_search_path() - - baselines = [] - for platform_dir in baseline_search_path: - if self.path_exists(self._filesystem.join(platform_dir, - baseline_filename)): - baselines.append((platform_dir, baseline_filename)) - - if not all_baselines and baselines: - return baselines - - # If it wasn't found in a platform directory, return the expected - # result in the test directory, even if no such file actually exists. - platform_dir = self.layout_tests_dir() - if self.path_exists(self._filesystem.join(platform_dir, - baseline_filename)): - baselines.append((platform_dir, baseline_filename)) - - if baselines: - return baselines - - return [(None, baseline_filename)] - - def expected_filename(self, filename, suffix): - """Given a test name, returns an absolute path to its expected results. - - If no expected results are found in any of the searched directories, - the directory in which the test itself is located will be returned. - The return value is in the format appropriate for the platform - (e.g., "\\" for path separators on windows). - - Args: - filename: absolute filename to test file - suffix: file suffix of the expected results, including dot; e.g. '.txt' - or '.png'. This should not be None, but may be an empty string. - platform: the most-specific directory name to use to build the - search list of directories, e.g., 'chromium-win', or - 'chromium-mac-leopard' (we follow the WebKit format) - - This routine is generic but is implemented here to live alongside - the other baseline and filename manipulation routines. - """ - platform_dir, baseline_filename = self.expected_baselines( - filename, suffix)[0] - if platform_dir: - return self._filesystem.join(platform_dir, baseline_filename) - return self._filesystem.join(self.layout_tests_dir(), baseline_filename) - - def expected_checksum(self, test): - """Returns the checksum of the image we expect the test to produce, or None if it is a text-only test.""" - path = self.expected_filename(test, '.checksum') - if not self.path_exists(path): - return None - return self._filesystem.read_text_file(path) - - def expected_image(self, test): - """Returns the image we expect the test to produce.""" - path = self.expected_filename(test, '.png') - if not self.path_exists(path): - return None - return self._filesystem.read_binary_file(path) - - def expected_text(self, test): - """Returns the text output we expect the test to produce.""" - # FIXME: DRT output is actually utf-8, but since we don't decode the - # output from DRT (instead treating it as a binary string), we read the - # baselines as a binary string, too. - path = self.expected_filename(test, '.txt') - if not self.path_exists(path): - return '' - text = self._filesystem.read_binary_file(path) - return text.strip("\r\n").replace("\r\n", "\n") + "\n" - - def filename_to_uri(self, filename): - """Convert a test file (which is an absolute path) to a URI.""" - LAYOUTTEST_HTTP_DIR = "http/tests/" - LAYOUTTEST_WEBSOCKET_DIR = "http/tests/websocket/tests/" - - relative_path = self.relative_test_filename(filename) - port = None - use_ssl = False - - if (relative_path.startswith(LAYOUTTEST_WEBSOCKET_DIR) - or relative_path.startswith(LAYOUTTEST_HTTP_DIR)): - relative_path = relative_path[len(LAYOUTTEST_HTTP_DIR):] - port = 8000 - - # Make http/tests/local run as local files. This is to mimic the - # logic in run-webkit-tests. - # - # TODO(dpranke): remove the media reference and the SSL reference? - if (port and not relative_path.startswith("local/") and - not relative_path.startswith("media/")): - if relative_path.startswith("ssl/"): - port += 443 - protocol = "https" - else: - protocol = "http" - return "%s://127.0.0.1:%u/%s" % (protocol, port, relative_path) - - return path.abspath_to_uri(os.path.abspath(filename)) - - def tests(self, paths): - """Return the list of tests found (relative to layout_tests_dir().""" - return test_files.find(self, paths) - - def test_dirs(self): - """Returns the list of top-level test directories. - - Used by --clobber-old-results.""" - layout_tests_dir = self.layout_tests_dir() - return filter(lambda x: self._filesystem.isdir(self._filesystem.join(layout_tests_dir, x)), - self._filesystem.listdir(layout_tests_dir)) - - def path_isdir(self, path): - """Return True if the path refers to a directory of tests.""" - # Used by test_expectations.py to apply rules to whole directories. - return self._filesystem.isdir(path) - - def path_exists(self, path): - """Return True if the path refers to an existing test or baseline.""" - # Used by test_expectations.py to determine if an entry refers to a - # valid test and by printing.py to determine if baselines exist. - return self._filesystem.exists(path) - - def driver_cmd_line(self): - """Prints the DRT command line that will be used.""" - driver = self.create_driver(0) - return driver.cmd_line() - - def update_baseline(self, path, data, encoding): - """Updates the baseline for a test. - - Args: - path: the actual path to use for baseline, not the path to - the test. This function is used to update either generic or - platform-specific baselines, but we can't infer which here. - data: contents of the baseline. - encoding: file encoding to use for the baseline. - """ - # FIXME: remove the encoding parameter in favor of text/binary - # functions. - if encoding is None: - self._filesystem.write_binary_file(path, data) - else: - self._filesystem.write_text_file(path, data) - - def uri_to_test_name(self, uri): - """Return the base layout test name for a given URI. - - This returns the test name for a given URI, e.g., if you passed in - "file:///src/LayoutTests/fast/html/keygen.html" it would return - "fast/html/keygen.html". - - """ - test = uri - if uri.startswith("file:///"): - prefix = path.abspath_to_uri(self.layout_tests_dir()) + "/" - return test[len(prefix):] - - if uri.startswith("http://127.0.0.1:8880/"): - # websocket tests - return test.replace('http://127.0.0.1:8880/', '') - - if uri.startswith("http://"): - # regular HTTP test - return test.replace('http://127.0.0.1:8000/', 'http/tests/') - - if uri.startswith("https://"): - return test.replace('https://127.0.0.1:8443/', 'http/tests/') - - raise NotImplementedError('unknown url type: %s' % uri) - - def layout_tests_dir(self): - """Return the absolute path to the top of the LayoutTests directory.""" - return self.path_from_webkit_base('LayoutTests') - - def skips_layout_test(self, test_name): - """Figures out if the givent test is being skipped or not. - - Test categories are handled as well.""" - for test_or_category in self.skipped_layout_tests(): - if test_or_category == test_name: - return True - category = self._filesystem.join(self.layout_tests_dir(), - test_or_category) - if (self._filesystem.isdir(category) and - test_name.startswith(test_or_category)): - return True - return False - - def maybe_make_directory(self, *path): - """Creates the specified directory if it doesn't already exist.""" - self._filesystem.maybe_make_directory(*path) - - def name(self): - """Return the name of the port (e.g., 'mac', 'chromium-win-xp'). - - Note that this is different from the test_platform_name(), which - may be different (e.g., 'win-xp' instead of 'chromium-win-xp'.""" - return self._name - - def get_option(self, name, default_value=None): - # FIXME: Eventually we should not have to do a test for - # hasattr(), and we should be able to just do - # self.options.value. See additional FIXME in the constructor. - if hasattr(self._options, name): - return getattr(self._options, name) - return default_value - - def set_option_default(self, name, default_value): - if not hasattr(self._options, name): - return setattr(self._options, name, default_value) - - def path_from_webkit_base(self, *comps): - """Returns the full path to path made by joining the top of the - WebKit source tree and the list of path components in |*comps|.""" - return self._config.path_from_webkit_base(*comps) - - def script_path(self, script_name): - return self._config.script_path(script_name) - - def path_to_test_expectations_file(self): - """Update the test expectations to the passed-in string. - - This is used by the rebaselining tool. Raises NotImplementedError - if the port does not use expectations files.""" - raise NotImplementedError('Port.path_to_test_expectations_file') - - def relative_test_filename(self, filename): - """Relative unix-style path for a filename under the LayoutTests - directory. Filenames outside the LayoutTests directory should raise - an error.""" - #assert(filename.startswith(self.layout_tests_dir())) - return filename[len(self.layout_tests_dir()) + 1:] - - def results_directory(self): - """Absolute path to the place to store the test results.""" - raise NotImplementedError('Port.results_directory') - - def setup_test_run(self): - """Perform port-specific work at the beginning of a test run.""" - pass - - def setup_environ_for_server(self): - """Perform port-specific work at the beginning of a server launch. - - Returns: - Operating-system's environment. - """ - return os.environ.copy() - - def show_results_html_file(self, results_filename): - """This routine should display the HTML file pointed at by - results_filename in a users' browser.""" - return self._user.open_url(results_filename) - - def create_driver(self, worker_number): - """Return a newly created base.Driver subclass for starting/stopping - the test driver.""" - raise NotImplementedError('Port.create_driver') - - def start_helper(self): - """If a port needs to reconfigure graphics settings or do other - things to ensure a known test configuration, it should override this - method.""" - pass - - def start_http_server(self): - """Start a web server if it is available. Do nothing if - it isn't. This routine is allowed to (and may) fail if a server - is already running.""" - if self.get_option('use_apache'): - self._http_server = apache_http_server.LayoutTestApacheHttpd(self, - self.get_option('results_directory')) - else: - self._http_server = http_server.Lighttpd(self, - self.get_option('results_directory')) - self._http_server.start() - - def start_websocket_server(self): - """Start a websocket server if it is available. Do nothing if - it isn't. This routine is allowed to (and may) fail if a server - is already running.""" - self._websocket_server = websocket_server.PyWebSocket(self, - self.get_option('results_directory')) - self._websocket_server.start() - - def acquire_http_lock(self): - self._http_lock = http_lock.HttpLock(None) - self._http_lock.wait_for_httpd_lock() - - def stop_helper(self): - """Shut down the test helper if it is running. Do nothing if - it isn't, or it isn't available. If a port overrides start_helper() - it must override this routine as well.""" - pass - - def stop_http_server(self): - """Shut down the http server if it is running. Do nothing if - it isn't, or it isn't available.""" - if self._http_server: - self._http_server.stop() - - def stop_websocket_server(self): - """Shut down the websocket server if it is running. Do nothing if - it isn't, or it isn't available.""" - if self._websocket_server: - self._websocket_server.stop() - - def release_http_lock(self): - if self._http_lock: - self._http_lock.cleanup_http_lock() - - def test_expectations(self): - """Returns the test expectations for this port. - - Basically this string should contain the equivalent of a - test_expectations file. See test_expectations.py for more details.""" - raise NotImplementedError('Port.test_expectations') - - def test_expectations_overrides(self): - """Returns an optional set of overrides for the test_expectations. - - This is used by ports that have code in two repositories, and where - it is possible that you might need "downstream" expectations that - temporarily override the "upstream" expectations until the port can - sync up the two repos.""" - return None - - def test_base_platform_names(self): - """Return a list of the 'base' platforms on your port. The base - platforms represent different architectures, operating systems, - or implementations (as opposed to different versions of a single - platform). For example, 'mac' and 'win' might be different base - platforms, wherease 'mac-tiger' and 'mac-leopard' might be - different platforms. This routine is used by the rebaselining tool - and the dashboards, and the strings correspond to the identifiers - in your test expectations (*not* necessarily the platform names - themselves).""" - raise NotImplementedError('Port.base_test_platforms') - - def test_platform_name(self): - """Returns the string that corresponds to the given platform name - in the test expectations. This may be the same as name(), or it - may be different. For example, chromium returns 'mac' for - 'chromium-mac'.""" - raise NotImplementedError('Port.test_platform_name') - - def test_platforms(self): - """Returns the list of test platform identifiers as used in the - test_expectations and on dashboards, the rebaselining tool, etc. - - Note that this is not necessarily the same as the list of ports, - which must be globally unique (e.g., both 'chromium-mac' and 'mac' - might return 'mac' as a test_platform name'.""" - raise NotImplementedError('Port.platforms') - - def test_platform_name_to_name(self, test_platform_name): - """Returns the Port platform name that corresponds to the name as - referenced in the expectations file. E.g., "mac" returns - "chromium-mac" on the Chromium ports.""" - raise NotImplementedError('Port.test_platform_name_to_name') - - def version(self): - """Returns a string indicating the version of a given platform, e.g. - '-leopard' or '-xp'. - - This is used to help identify the exact port when parsing test - expectations, determining search paths, and logging information.""" - raise NotImplementedError('Port.version') - - def test_repository_paths(self): - """Returns a list of (repository_name, repository_path) tuples - of its depending code base. By default it returns a list that only - contains a ('webkit', <webkitRepossitoryPath>) tuple. - """ - return [('webkit', self.layout_tests_dir())] - - - _WDIFF_DEL = '##WDIFF_DEL##' - _WDIFF_ADD = '##WDIFF_ADD##' - _WDIFF_END = '##WDIFF_END##' - - def _format_wdiff_output_as_html(self, wdiff): - wdiff = cgi.escape(wdiff) - wdiff = wdiff.replace(self._WDIFF_DEL, "<span class=del>") - wdiff = wdiff.replace(self._WDIFF_ADD, "<span class=add>") - wdiff = wdiff.replace(self._WDIFF_END, "</span>") - html = "<head><style>.del { background: #faa; } " - html += ".add { background: #afa; }</style></head>" - html += "<pre>%s</pre>" % wdiff - return html - - def _wdiff_command(self, actual_filename, expected_filename): - executable = self._path_to_wdiff() - return [executable, - "--start-delete=%s" % self._WDIFF_DEL, - "--end-delete=%s" % self._WDIFF_END, - "--start-insert=%s" % self._WDIFF_ADD, - "--end-insert=%s" % self._WDIFF_END, - actual_filename, - expected_filename] - - @staticmethod - def _handle_wdiff_error(script_error): - # Exit 1 means the files differed, any other exit code is an error. - if script_error.exit_code != 1: - raise script_error - - def _run_wdiff(self, actual_filename, expected_filename): - """Runs wdiff and may throw exceptions. - This is mostly a hook for unit testing.""" - # Diffs are treated as binary as they may include multiple files - # with conflicting encodings. Thus we do not decode the output. - command = self._wdiff_command(actual_filename, expected_filename) - wdiff = self._executive.run_command(command, decode_output=False, - error_handler=self._handle_wdiff_error) - return self._format_wdiff_output_as_html(wdiff) - - def wdiff_text(self, actual_filename, expected_filename): - """Returns a string of HTML indicating the word-level diff of the - contents of the two filenames. Returns an empty string if word-level - diffing isn't available.""" - if not self._wdiff_available: - return "" - try: - # It's possible to raise a ScriptError we pass wdiff invalid paths. - return self._run_wdiff(actual_filename, expected_filename) - except OSError, e: - if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]: - # Silently ignore cases where wdiff is missing. - self._wdiff_available = False - return "" - raise - - # This is a class variable so we can test error output easily. - _pretty_patch_error_html = "Failed to run PrettyPatch, see error log." - - def pretty_patch_text(self, diff_path): - if not self._pretty_patch_available: - return self._pretty_patch_error_html - command = ("ruby", "-I", os.path.dirname(self._pretty_patch_path), - self._pretty_patch_path, diff_path) - try: - # Diffs are treated as binary (we pass decode_output=False) as they - # may contain multiple files of conflicting encodings. - return self._executive.run_command(command, decode_output=False) - except OSError, e: - # If the system is missing ruby log the error and stop trying. - self._pretty_patch_available = False - _log.error("Failed to run PrettyPatch (%s): %s" % (command, e)) - return self._pretty_patch_error_html - except ScriptError, e: - # If ruby failed to run for some reason, log the command - # output and stop trying. - self._pretty_patch_available = False - _log.error("Failed to run PrettyPatch (%s):\n%s" % (command, - e.message_with_output())) - return self._pretty_patch_error_html - - def default_configuration(self): - return self._config.default_configuration() - - # - # PROTECTED ROUTINES - # - # The routines below should only be called by routines in this class - # or any of its subclasses. - # - def _webkit_build_directory(self, args): - return self._config.build_directory(args[0]) - - def _path_to_apache(self): - """Returns the full path to the apache binary. - - This is needed only by ports that use the apache_http_server module.""" - raise NotImplementedError('Port.path_to_apache') - - def _path_to_apache_config_file(self): - """Returns the full path to the apache binary. - - This is needed only by ports that use the apache_http_server module.""" - raise NotImplementedError('Port.path_to_apache_config_file') - - def _path_to_driver(self, configuration=None): - """Returns the full path to the test driver (DumpRenderTree).""" - raise NotImplementedError('Port._path_to_driver') - - def _path_to_webcore_library(self): - """Returns the full path to a built copy of WebCore.""" - raise NotImplementedError('Port.path_to_webcore_library') - - def _path_to_helper(self): - """Returns the full path to the layout_test_helper binary, which - is used to help configure the system for the test run, or None - if no helper is needed. - - This is likely only used by start/stop_helper().""" - raise NotImplementedError('Port._path_to_helper') - - def _path_to_image_diff(self): - """Returns the full path to the image_diff binary, or None if it - is not available. - - This is likely used only by diff_image()""" - raise NotImplementedError('Port.path_to_image_diff') - - def _path_to_lighttpd(self): - """Returns the path to the LigHTTPd binary. - - This is needed only by ports that use the http_server.py module.""" - raise NotImplementedError('Port._path_to_lighttpd') - - def _path_to_lighttpd_modules(self): - """Returns the path to the LigHTTPd modules directory. - - This is needed only by ports that use the http_server.py module.""" - raise NotImplementedError('Port._path_to_lighttpd_modules') - - def _path_to_lighttpd_php(self): - """Returns the path to the LigHTTPd PHP executable. - - This is needed only by ports that use the http_server.py module.""" - raise NotImplementedError('Port._path_to_lighttpd_php') - - def _path_to_wdiff(self): - """Returns the full path to the wdiff binary, or None if it is - not available. - - This is likely used only by wdiff_text()""" - raise NotImplementedError('Port._path_to_wdiff') - - def _shut_down_http_server(self, pid): - """Forcefully and synchronously kills the web server. - - This routine should only be called from http_server.py or its - subclasses.""" - raise NotImplementedError('Port._shut_down_http_server') - - def _webkit_baseline_path(self, platform): - """Return the full path to the top of the baseline tree for a - given platform.""" - return self._filesystem.join(self.layout_tests_dir(), 'platform', - platform) - - -class Driver: - """Abstract interface for the DumpRenderTree interface.""" - - def __init__(self, port, worker_number): - """Initialize a Driver to subsequently run tests. - - Typically this routine will spawn DumpRenderTree in a config - ready for subsequent input. - - port - reference back to the port object. - worker_number - identifier for a particular worker/driver instance - """ - raise NotImplementedError('Driver.__init__') - - def run_test(self, test_input): - """Run a single test and return the results. - - Note that it is okay if a test times out or crashes and leaves - the driver in an indeterminate state. The upper layers of the program - are responsible for cleaning up and ensuring things are okay. - - Args: - test_input: a TestInput object - - Returns a TestOutput object. - """ - raise NotImplementedError('Driver.run_test') - - # FIXME: This is static so we can test it w/o creating a Base instance. - @classmethod - def _command_wrapper(cls, wrapper_option): - # Hook for injecting valgrind or other runtime instrumentation, - # used by e.g. tools/valgrind/valgrind_tests.py. - wrapper = [] - browser_wrapper = os.environ.get("BROWSER_WRAPPER", None) - if browser_wrapper: - # FIXME: There seems to be no reason to use BROWSER_WRAPPER over --wrapper. - # Remove this code any time after the date listed below. - _log.error("BROWSER_WRAPPER is deprecated, please use --wrapper instead.") - _log.error("BROWSER_WRAPPER will be removed any time after June 1st 2010 and your scripts will break.") - wrapper += [browser_wrapper] - - if wrapper_option: - wrapper += shlex.split(wrapper_option) - return wrapper - - def poll(self): - """Returns None if the Driver is still running. Returns the returncode - if it has exited.""" - raise NotImplementedError('Driver.poll') - - def stop(self): - raise NotImplementedError('Driver.stop') diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py deleted file mode 100644 index 8d586e3..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/base_unittest.py +++ /dev/null @@ -1,315 +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 optparse -import os -import sys -import tempfile -import unittest - -from webkitpy.common.system.executive import Executive, ScriptError -from webkitpy.common.system import executive_mock -from webkitpy.common.system import filesystem -from webkitpy.common.system import outputcapture -from webkitpy.common.system.path import abspath_to_uri -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool import mocktool - -import base -import config -import config_mock - - -class PortTest(unittest.TestCase): - def test_format_wdiff_output_as_html(self): - output = "OUTPUT %s %s %s" % (base.Port._WDIFF_DEL, base.Port._WDIFF_ADD, base.Port._WDIFF_END) - html = base.Port()._format_wdiff_output_as_html(output) - expected_html = "<head><style>.del { background: #faa; } .add { background: #afa; }</style></head><pre>OUTPUT <span class=del> <span class=add> </span></pre>" - self.assertEqual(html, expected_html) - - def test_wdiff_command(self): - port = base.Port() - port._path_to_wdiff = lambda: "/path/to/wdiff" - command = port._wdiff_command("/actual/path", "/expected/path") - expected_command = [ - "/path/to/wdiff", - "--start-delete=##WDIFF_DEL##", - "--end-delete=##WDIFF_END##", - "--start-insert=##WDIFF_ADD##", - "--end-insert=##WDIFF_END##", - "/actual/path", - "/expected/path", - ] - self.assertEqual(command, expected_command) - - def _file_with_contents(self, contents, encoding="utf-8"): - new_file = tempfile.NamedTemporaryFile() - new_file.write(contents.encode(encoding)) - new_file.flush() - return new_file - - def test_pretty_patch_os_error(self): - port = base.Port(executive=executive_mock.MockExecutive2(exception=OSError)) - oc = outputcapture.OutputCapture() - oc.capture_output() - self.assertEqual(port.pretty_patch_text("patch.txt"), - port._pretty_patch_error_html) - - # This tests repeated calls to make sure we cache the result. - self.assertEqual(port.pretty_patch_text("patch.txt"), - port._pretty_patch_error_html) - oc.restore_output() - - def test_pretty_patch_script_error(self): - # FIXME: This is some ugly white-box test hacking ... - base._pretty_patch_available = True - port = base.Port(executive=executive_mock.MockExecutive2(exception=ScriptError)) - self.assertEqual(port.pretty_patch_text("patch.txt"), - port._pretty_patch_error_html) - - # This tests repeated calls to make sure we cache the result. - self.assertEqual(port.pretty_patch_text("patch.txt"), - port._pretty_patch_error_html) - - def test_run_wdiff(self): - executive = Executive() - # This may fail on some systems. We could ask the port - # object for the wdiff path, but since we don't know what - # port object to use, this is sufficient for now. - try: - wdiff_path = executive.run_command(["which", "wdiff"]).rstrip() - except Exception, e: - wdiff_path = None - - port = base.Port() - port._path_to_wdiff = lambda: wdiff_path - - if wdiff_path: - # "with tempfile.NamedTemporaryFile() as actual" does not seem to work in Python 2.5 - actual = self._file_with_contents(u"foo") - expected = self._file_with_contents(u"bar") - wdiff = port._run_wdiff(actual.name, expected.name) - expected_wdiff = "<head><style>.del { background: #faa; } .add { background: #afa; }</style></head><pre><span class=del>foo</span><span class=add>bar</span></pre>" - self.assertEqual(wdiff, expected_wdiff) - # Running the full wdiff_text method should give the same result. - port._wdiff_available = True # In case it's somehow already disabled. - wdiff = port.wdiff_text(actual.name, expected.name) - self.assertEqual(wdiff, expected_wdiff) - # wdiff should still be available after running wdiff_text with a valid diff. - self.assertTrue(port._wdiff_available) - actual.close() - expected.close() - - # Bogus paths should raise a script error. - self.assertRaises(ScriptError, port._run_wdiff, "/does/not/exist", "/does/not/exist2") - self.assertRaises(ScriptError, port.wdiff_text, "/does/not/exist", "/does/not/exist2") - # wdiff will still be available after running wdiff_text with invalid paths. - self.assertTrue(port._wdiff_available) - base._wdiff_available = True - - # If wdiff does not exist _run_wdiff should throw an OSError. - port._path_to_wdiff = lambda: "/invalid/path/to/wdiff" - self.assertRaises(OSError, port._run_wdiff, "foo", "bar") - - # wdiff_text should not throw an error if wdiff does not exist. - self.assertEqual(port.wdiff_text("foo", "bar"), "") - # However wdiff should not be available after running wdiff_text if wdiff is missing. - self.assertFalse(port._wdiff_available) - - def test_diff_text(self): - port = base.Port() - # Make sure that we don't run into decoding exceptions when the - # filenames are unicode, with regular or malformed input (expected or - # actual input is always raw bytes, not unicode). - port.diff_text('exp', 'act', 'exp.txt', 'act.txt') - port.diff_text('exp', 'act', u'exp.txt', 'act.txt') - port.diff_text('exp', 'act', u'a\xac\u1234\u20ac\U00008000', 'act.txt') - - port.diff_text('exp' + chr(255), 'act', 'exp.txt', 'act.txt') - port.diff_text('exp' + chr(255), 'act', u'exp.txt', 'act.txt') - - # Though expected and actual files should always be read in with no - # encoding (and be stored as str objects), test unicode inputs just to - # be safe. - port.diff_text(u'exp', 'act', 'exp.txt', 'act.txt') - port.diff_text( - u'a\xac\u1234\u20ac\U00008000', 'act', 'exp.txt', 'act.txt') - - # And make sure we actually get diff output. - diff = port.diff_text('foo', 'bar', 'exp.txt', 'act.txt') - self.assertTrue('foo' in diff) - self.assertTrue('bar' in diff) - self.assertTrue('exp.txt' in diff) - self.assertTrue('act.txt' in diff) - self.assertFalse('nosuchthing' in diff) - - def test_default_configuration_notfound(self): - # Test that we delegate to the config object properly. - port = base.Port(config=config_mock.MockConfig(default_configuration='default')) - self.assertEqual(port.default_configuration(), 'default') - - def test_layout_tests_skipping(self): - port = base.Port() - port.skipped_layout_tests = lambda: ['foo/bar.html', 'media'] - self.assertTrue(port.skips_layout_test('foo/bar.html')) - self.assertTrue(port.skips_layout_test('media/video-zoom.html')) - self.assertFalse(port.skips_layout_test('foo/foo.html')) - - def test_setup_test_run(self): - port = base.Port() - # This routine is a no-op. We just test it for coverage. - port.setup_test_run() - - def test_test_dirs(self): - port = base.Port() - dirs = port.test_dirs() - self.assertTrue('canvas' in dirs) - self.assertTrue('css2.1' in dirs) - - def test_filename_to_uri(self): - port = base.Port() - layout_test_dir = port.layout_tests_dir() - test_file = os.path.join(layout_test_dir, "foo", "bar.html") - - # On Windows, absolute paths are of the form "c:\foo.txt". However, - # all current browsers (except for Opera) normalize file URLs by - # prepending an additional "/" as if the absolute path was - # "/c:/foo.txt". This means that all file URLs end up with "file:///" - # at the beginning. - if sys.platform == 'win32': - prefix = "file:///" - path = test_file.replace("\\", "/") - else: - prefix = "file://" - path = test_file - - self.assertEqual(port.filename_to_uri(test_file), - abspath_to_uri(test_file)) - - def test_get_option__set(self): - options, args = optparse.OptionParser().parse_args([]) - options.foo = 'bar' - port = base.Port(options=options) - self.assertEqual(port.get_option('foo'), 'bar') - - def test_get_option__unset(self): - port = base.Port() - self.assertEqual(port.get_option('foo'), None) - - def test_get_option__default(self): - port = base.Port() - self.assertEqual(port.get_option('foo', 'bar'), 'bar') - - def test_set_option_default__unset(self): - port = base.Port() - port.set_option_default('foo', 'bar') - self.assertEqual(port.get_option('foo'), 'bar') - - def test_set_option_default__set(self): - options, args = optparse.OptionParser().parse_args([]) - options.foo = 'bar' - port = base.Port(options=options) - # This call should have no effect. - port.set_option_default('foo', 'new_bar') - self.assertEqual(port.get_option('foo'), 'bar') - - def test_name__unset(self): - port = base.Port() - self.assertEqual(port.name(), None) - - def test_name__set(self): - port = base.Port(port_name='foo') - self.assertEqual(port.name(), 'foo') - - -class VirtualTest(unittest.TestCase): - """Tests that various methods expected to be virtual are.""" - def assertVirtual(self, method, *args, **kwargs): - self.assertRaises(NotImplementedError, method, *args, **kwargs) - - def test_virtual_methods(self): - port = base.Port() - self.assertVirtual(port.baseline_path) - self.assertVirtual(port.baseline_search_path) - self.assertVirtual(port.check_build, None) - self.assertVirtual(port.check_image_diff) - self.assertVirtual(port.create_driver, 0) - self.assertVirtual(port.diff_image, None, None) - self.assertVirtual(port.path_to_test_expectations_file) - self.assertVirtual(port.test_platform_name) - self.assertVirtual(port.results_directory) - self.assertVirtual(port.test_expectations) - self.assertVirtual(port.test_base_platform_names) - self.assertVirtual(port.test_platform_name) - self.assertVirtual(port.test_platforms) - self.assertVirtual(port.test_platform_name_to_name, None) - self.assertVirtual(port.version) - self.assertVirtual(port._path_to_apache) - self.assertVirtual(port._path_to_apache_config_file) - self.assertVirtual(port._path_to_driver) - self.assertVirtual(port._path_to_helper) - self.assertVirtual(port._path_to_image_diff) - self.assertVirtual(port._path_to_lighttpd) - self.assertVirtual(port._path_to_lighttpd_modules) - self.assertVirtual(port._path_to_lighttpd_php) - self.assertVirtual(port._path_to_wdiff) - self.assertVirtual(port._shut_down_http_server, None) - - def test_virtual_driver_method(self): - self.assertRaises(NotImplementedError, base.Driver, base.Port(), - 0) - - def test_virtual_driver_methods(self): - class VirtualDriver(base.Driver): - def __init__(self): - pass - - driver = VirtualDriver() - self.assertVirtual(driver.run_test, None) - self.assertVirtual(driver.poll) - self.assertVirtual(driver.stop) - - -class DriverTest(unittest.TestCase): - - def _assert_wrapper(self, wrapper_string, expected_wrapper): - wrapper = base.Driver._command_wrapper(wrapper_string) - self.assertEqual(wrapper, expected_wrapper) - - def test_command_wrapper(self): - self._assert_wrapper(None, []) - self._assert_wrapper("valgrind", ["valgrind"]) - - # Validate that shlex works as expected. - command_with_spaces = "valgrind --smc-check=\"check with spaces!\" --foo" - expected_parse = ["valgrind", "--smc-check=check with spaces!", "--foo"] - self._assert_wrapper(command_with_spaces, expected_parse) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py deleted file mode 100644 index 8fe685a..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py +++ /dev/null @@ -1,555 +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. - -"""Chromium implementations of the Port interface.""" - -from __future__ import with_statement - -import codecs -import errno -import logging -import os -import re -import shutil -import signal -import subprocess -import sys -import tempfile -import time -import webbrowser - -from webkitpy.common.system.path import cygpath -from webkitpy.layout_tests.layout_package import test_expectations -from webkitpy.layout_tests.layout_package import test_output - -import base -import http_server - -# Chromium DRT on OSX uses WebKitDriver. -if sys.platform == 'darwin': - import webkit - -import websocket_server - -_log = logging.getLogger("webkitpy.layout_tests.port.chromium") - - -# FIXME: This function doesn't belong in this package. -def check_file_exists(path_to_file, file_description, override_step=None, - logging=True): - """Verify the file is present where expected or log an error. - - Args: - file_name: The (human friendly) name or description of the file - you're looking for (e.g., "HTTP Server"). Used for error logging. - override_step: An optional string to be logged if the check fails. - logging: Whether or not log the error messages.""" - if not os.path.exists(path_to_file): - if logging: - _log.error('Unable to find %s' % file_description) - _log.error(' at %s' % path_to_file) - if override_step: - _log.error(' %s' % override_step) - _log.error('') - return False - return True - - -class ChromiumPort(base.Port): - """Abstract base class for Chromium implementations of the Port class.""" - - def __init__(self, **kwargs): - base.Port.__init__(self, **kwargs) - self._chromium_base_dir = None - - def baseline_path(self): - return self._webkit_baseline_path(self._name) - - def check_build(self, needs_http): - result = True - - dump_render_tree_binary_path = self._path_to_driver() - result = check_file_exists(dump_render_tree_binary_path, - 'test driver') and result - if result and self.get_option('build'): - result = self._check_driver_build_up_to_date( - self.get_option('configuration')) - else: - _log.error('') - - helper_path = self._path_to_helper() - if helper_path: - result = check_file_exists(helper_path, - 'layout test helper') and result - - if self.get_option('pixel_tests'): - result = self.check_image_diff( - 'To override, invoke with --no-pixel-tests') and result - - # It's okay if pretty patch isn't available, but we will at - # least log a message. - self.check_pretty_patch() - - return result - - def check_sys_deps(self, needs_http): - cmd = [self._path_to_driver(), '--check-layout-test-sys-deps'] - if self._executive.run_command(cmd, return_exit_code=True): - _log.error('System dependencies check failed.') - _log.error('To override, invoke with --nocheck-sys-deps') - _log.error('') - return False - return True - - def check_image_diff(self, override_step=None, logging=True): - image_diff_path = self._path_to_image_diff() - return check_file_exists(image_diff_path, 'image diff exe', - override_step, logging) - - def diff_image(self, expected_contents, actual_contents, - diff_filename=None): - executable = self._path_to_image_diff() - - tempdir = tempfile.mkdtemp() - expected_filename = os.path.join(tempdir, "expected.png") - with open(expected_filename, 'w+b') as file: - file.write(expected_contents) - actual_filename = os.path.join(tempdir, "actual.png") - with open(actual_filename, 'w+b') as file: - file.write(actual_contents) - - if diff_filename: - cmd = [executable, '--diff', expected_filename, - actual_filename, diff_filename] - else: - cmd = [executable, expected_filename, actual_filename] - - result = True - try: - exit_code = self._executive.run_command(cmd, return_exit_code=True) - if exit_code == 0: - # The images are the same. - result = False - elif exit_code != 1: - _log.error("image diff returned an exit code of " - + str(exit_code)) - # Returning False here causes the script to think that we - # successfully created the diff even though we didn't. If - # we return True, we think that the images match but the hashes - # don't match. - # FIXME: Figure out why image_diff returns other values. - result = False - except OSError, e: - if e.errno == errno.ENOENT or e.errno == errno.EACCES: - _compare_available = False - else: - raise e - finally: - shutil.rmtree(tempdir, ignore_errors=True) - return result - - def driver_name(self): - if self._options.use_drt: - return "DumpRenderTree" - return "test_shell" - - def path_from_chromium_base(self, *comps): - """Returns the full path to path made by joining the top of the - Chromium source tree and the list of path components in |*comps|.""" - if not self._chromium_base_dir: - abspath = os.path.abspath(__file__) - offset = abspath.find('third_party') - if offset == -1: - self._chromium_base_dir = os.path.join( - abspath[0:abspath.find('WebKitTools')], - 'WebKit', 'chromium') - else: - self._chromium_base_dir = abspath[0:offset] - return os.path.join(self._chromium_base_dir, *comps) - - def path_to_test_expectations_file(self): - return self.path_from_webkit_base('LayoutTests', 'platform', - 'chromium', 'test_expectations.txt') - - def results_directory(self): - try: - return self.path_from_chromium_base('webkit', - self.get_option('configuration'), - self.get_option('results_directory')) - except AssertionError: - return self._build_path(self.get_option('configuration'), - self.get_option('results_directory')) - - def setup_test_run(self): - # Delete the disk cache if any to ensure a clean test run. - dump_render_tree_binary_path = self._path_to_driver() - cachedir = os.path.split(dump_render_tree_binary_path)[0] - cachedir = os.path.join(cachedir, "cache") - if os.path.exists(cachedir): - shutil.rmtree(cachedir) - - def create_driver(self, worker_number): - """Starts a new Driver and returns a handle to it.""" - if self.get_option('use_drt') and sys.platform == 'darwin': - return webkit.WebKitDriver(self, worker_number) - return ChromiumDriver(self, worker_number) - - def start_helper(self): - helper_path = self._path_to_helper() - if helper_path: - _log.debug("Starting layout helper %s" % helper_path) - # Note: Not thread safe: http://bugs.python.org/issue2320 - self._helper = subprocess.Popen([helper_path], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None) - is_ready = self._helper.stdout.readline() - if not is_ready.startswith('ready'): - _log.error("layout_test_helper failed to be ready") - - def stop_helper(self): - if self._helper: - _log.debug("Stopping layout test helper") - self._helper.stdin.write("x\n") - self._helper.stdin.close() - # wait() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - self._helper.wait() - - def test_base_platform_names(self): - return ('linux', 'mac', 'win') - - def test_expectations(self): - """Returns the test expectations for this port. - - Basically this string should contain the equivalent of a - test_expectations file. See test_expectations.py for more details.""" - expectations_path = self.path_to_test_expectations_file() - with codecs.open(expectations_path, "r", "utf-8") as file: - return file.read() - - def test_expectations_overrides(self): - # FIXME: This drt_overrides handling should be removed when we switch - # from tes_shell to DRT. - drt_overrides = '' - if self.get_option('use_drt'): - drt_overrides_path = self.path_from_webkit_base('LayoutTests', - 'platform', 'chromium', 'drt_expectations.txt') - if os.path.exists(drt_overrides_path): - with codecs.open(drt_overrides_path, "r", "utf-8") as file: - drt_overrides = file.read() - - try: - overrides_path = self.path_from_chromium_base('webkit', 'tools', - 'layout_tests', 'test_expectations.txt') - except AssertionError: - return None - if not os.path.exists(overrides_path): - return None - with codecs.open(overrides_path, "r", "utf-8") as file: - return file.read() + drt_overrides - - def skipped_layout_tests(self, extra_test_files=None): - expectations_str = self.test_expectations() - overrides_str = self.test_expectations_overrides() - test_platform_name = self.test_platform_name() - is_debug_mode = False - - all_test_files = self.tests([]) - if extra_test_files: - all_test_files.update(extra_test_files) - - expectations = test_expectations.TestExpectations( - self, all_test_files, expectations_str, test_platform_name, - is_debug_mode, is_lint_mode=True, overrides=overrides_str) - tests_dir = self.layout_tests_dir() - return [self.relative_test_filename(test) - for test in expectations.get_tests_with_result_type(test_expectations.SKIP)] - - def test_platform_names(self): - return self.test_base_platform_names() + ('win-xp', - 'win-vista', 'win-7') - - def test_platform_name_to_name(self, test_platform_name): - if test_platform_name in self.test_platform_names(): - return 'chromium-' + test_platform_name - raise ValueError('Unsupported test_platform_name: %s' % - test_platform_name) - - def test_repository_paths(self): - # Note: for JSON file's backward-compatibility we use 'chrome' rather - # than 'chromium' here. - repos = super(ChromiumPort, self).test_repository_paths() - repos.append(('chrome', self.path_from_chromium_base())) - return repos - - # - # PROTECTED METHODS - # - # These routines should only be called by other methods in this file - # or any subclasses. - # - - def _check_driver_build_up_to_date(self, configuration): - if configuration in ('Debug', 'Release'): - try: - debug_path = self._path_to_driver('Debug') - release_path = self._path_to_driver('Release') - - debug_mtime = os.stat(debug_path).st_mtime - release_mtime = os.stat(release_path).st_mtime - - if (debug_mtime > release_mtime and configuration == 'Release' or - release_mtime > debug_mtime and configuration == 'Debug'): - _log.warning('You are not running the most ' - 'recent DumpRenderTree binary. You need to ' - 'pass --debug or not to select between ' - 'Debug and Release.') - _log.warning('') - # This will fail if we don't have both a debug and release binary. - # That's fine because, in this case, we must already be running the - # most up-to-date one. - except OSError: - pass - return True - - def _chromium_baseline_path(self, platform): - if platform is None: - platform = self.name() - return self.path_from_webkit_base('LayoutTests', 'platform', platform) - - def _convert_path(self, path): - """Handles filename conversion for subprocess command line args.""" - # See note above in diff_image() for why we need this. - if sys.platform == 'cygwin': - return cygpath(path) - return path - - def _path_to_image_diff(self): - binary_name = 'image_diff' - if self.get_option('use_drt'): - binary_name = 'ImageDiff' - return self._build_path(self.get_option('configuration'), binary_name) - - -class ChromiumDriver(base.Driver): - """Abstract interface for test_shell.""" - - def __init__(self, port, worker_number): - self._port = port - self._worker_number = worker_number - self._image_path = None - if self._port.get_option('pixel_tests'): - self._image_path = os.path.join( - self._port.get_option('results_directory'), - 'png_result%s.png' % self._worker_number) - - def cmd_line(self): - cmd = self._command_wrapper(self._port.get_option('wrapper')) - cmd.append(self._port._path_to_driver()) - if self._port.get_option('pixel_tests'): - # See note above in diff_image() for why we need _convert_path(). - cmd.append("--pixel-tests=" + - self._port._convert_path(self._image_path)) - - if self._port.get_option('use_drt'): - cmd.append('--test-shell') - else: - cmd.append('--layout-tests') - - if self._port.get_option('startup_dialog'): - cmd.append('--testshell-startup-dialog') - - if self._port.get_option('gp_fault_error_box'): - cmd.append('--gp-fault-error-box') - - if self._port.get_option('js_flags') is not None: - cmd.append('--js-flags="' + self._port.get_option('js_flags') + '"') - - if self._port.get_option('multiple_loads') > 0: - cmd.append('--multiple-loads=' + str(self._port.get_option('multiple_loads'))) - - if self._port.get_option('accelerated_compositing'): - cmd.append('--enable-accelerated-compositing') - - if self._port.get_option('accelerated_2d_canvas'): - cmd.append('--enable-accelerated-2d-canvas') - return cmd - - def start(self): - # FIXME: Should be an error to call this method twice. - cmd = self.cmd_line() - - # 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. - close_flag = sys.platform not in ('win32', 'cygwin') - self._proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - close_fds=close_flag) - - def poll(self): - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - return self._proc.poll() - - def _write_command_and_read_line(self, input=None): - """Returns a tuple: (line, did_crash)""" - try: - if input: - if isinstance(input, unicode): - # TestShell expects utf-8 - input = input.encode("utf-8") - self._proc.stdin.write(input) - # DumpRenderTree text output is always UTF-8. However some tests - # (e.g. webarchive) may spit out binary data instead of text so we - # don't bother to decode the output (for either DRT or test_shell). - line = self._proc.stdout.readline() - # We could assert() here that line correctly decodes as UTF-8. - return (line, False) - except IOError, e: - _log.error("IOError communicating w/ test_shell: " + str(e)) - return (None, True) - - def _test_shell_command(self, uri, timeoutms, checksum): - cmd = uri - if timeoutms: - cmd += ' ' + str(timeoutms) - if checksum: - cmd += ' ' + checksum - cmd += "\n" - return cmd - - def _output_image(self): - """Returns the image output which driver generated.""" - png_path = self._image_path - if png_path and os.path.isfile(png_path): - with open(png_path, 'rb') as image_file: - return image_file.read() - else: - return None - - def _output_image_with_retry(self): - # Retry a few more times because open() sometimes fails on Windows, - # raising "IOError: [Errno 13] Permission denied:" - retry_num = 50 - timeout_seconds = 5.0 - for i in range(retry_num): - try: - return self._output_image() - except IOError, e: - if e.errno == errno.EACCES: - time.sleep(timeout_seconds / retry_num) - else: - raise e - return self._output_image() - - def run_test(self, test_input): - output = [] - error = [] - crash = False - timeout = False - actual_uri = None - actual_checksum = None - - start_time = time.time() - - uri = self._port.filename_to_uri(test_input.filename) - cmd = self._test_shell_command(uri, test_input.timeout, - test_input.image_hash) - (line, crash) = self._write_command_and_read_line(input=cmd) - - while not crash and line.rstrip() != "#EOF": - # Make sure we haven't crashed. - if line == '' and self.poll() is not None: - # This is hex code 0xc000001d, which is used for abrupt - # termination. This happens if we hit ctrl+c from the prompt - # and we happen to be waiting on test_shell. - # sdoyon: Not sure for which OS and in what circumstances the - # above code is valid. What works for me under Linux to detect - # ctrl+c is for the subprocess returncode to be negative - # SIGINT. And that agrees with the subprocess documentation. - if (-1073741510 == self._proc.returncode or - - signal.SIGINT == self._proc.returncode): - raise KeyboardInterrupt - crash = True - break - - # Don't include #URL lines in our output - if line.startswith("#URL:"): - actual_uri = line.rstrip()[5:] - if uri != actual_uri: - # GURL capitalizes the drive letter of a file URL. - if (not re.search("^file:///[a-z]:", uri) or - uri.lower() != actual_uri.lower()): - _log.fatal("Test got out of sync:\n|%s|\n|%s|" % - (uri, actual_uri)) - raise AssertionError("test out of sync") - elif line.startswith("#MD5:"): - actual_checksum = line.rstrip()[5:] - elif line.startswith("#TEST_TIMED_OUT"): - timeout = True - # Test timed out, but we still need to read until #EOF. - elif actual_uri: - output.append(line) - else: - error.append(line) - - (line, crash) = self._write_command_and_read_line(input=None) - - run_time = time.time() - start_time - return test_output.TestOutput( - ''.join(output), self._output_image_with_retry(), actual_checksum, - crash, run_time, timeout, ''.join(error)) - - def stop(self): - if self._proc: - self._proc.stdin.close() - self._proc.stdout.close() - if self._proc.stderr: - self._proc.stderr.close() - if sys.platform not in ('win32', 'cygwin'): - # Closing stdin/stdout/stderr hangs sometimes on OS X, - # (see __init__(), above), and anyway we don't want to hang - # the harness if test_shell is buggy, so we wait a couple - # seconds to give test_shell a chance to clean up, but then - # force-kill the process if necessary. - KILL_TIMEOUT = 3.0 - timeout = time.time() + KILL_TIMEOUT - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - while self._proc.poll() is None and time.time() < timeout: - time.sleep(0.1) - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - if self._proc.poll() is None: - _log.warning('stopping test driver timed out, ' - 'killing it') - self._port._executive.kill_process(self._proc.pid) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py deleted file mode 100644 index 54a0fee..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu.py +++ /dev/null @@ -1,154 +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. - -# 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 sys - -import chromium_linux -import chromium_mac -import chromium_win - - -def get(**kwargs): - """Some tests have slightly different results when run while using - hardware acceleration. In those cases, we prepend an additional directory - to the baseline paths.""" - port_name = kwargs.get('port_name', None) - if port_name == 'chromium-gpu': - if sys.platform in ('cygwin', 'win32'): - port_name = 'chromium-gpu-win' - elif sys.platform == 'linux2': - port_name = 'chromium-gpu-linux' - elif sys.platform == 'darwin': - port_name = 'chromium-gpu-mac' - else: - raise NotImplementedError('unsupported platform: %s' % - sys.platform) - - if port_name == 'chromium-gpu-linux': - return ChromiumGpuLinuxPort(**kwargs) - - if port_name.startswith('chromium-gpu-mac'): - return ChromiumGpuMacPort(**kwargs) - - if port_name.startswith('chromium-gpu-win'): - return ChromiumGpuWinPort(**kwargs) - - raise NotImplementedError('unsupported port: %s' % port_name) - - -def _set_gpu_options(options): - if options: - if options.accelerated_compositing is None: - options.accelerated_compositing = True - if options.accelerated_2d_canvas is None: - options.accelerated_2d_canvas = True - if options.use_drt is None: - options.use_drt = True - - # FIXME: Remove this after http://codereview.chromium.org/5133001/ is enabled - # on the bots. - if options.builder_name is not None and not ' - GPU' in options.builder_name: - options.builder_name = options.builder_name + ' - GPU' - - -def _gpu_overrides(port): - try: - overrides_path = port.path_from_chromium_base('webkit', 'tools', - 'layout_tests', 'test_expectations_gpu.txt') - except AssertionError: - return None - if not os.path.exists(overrides_path): - return None - with codecs.open(overrides_path, "r", "utf-8") as file: - return file.read() - - -class ChromiumGpuLinuxPort(chromium_linux.ChromiumLinuxPort): - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'chromium-gpu-linux') - _set_gpu_options(kwargs.get('options')) - chromium_linux.ChromiumLinuxPort.__init__(self, **kwargs) - - def baseline_search_path(self): - # Mimic the Linux -> Win expectations fallback in the ordinary Chromium port. - return (map(self._webkit_baseline_path, ['chromium-gpu-linux', 'chromium-gpu-win', 'chromium-gpu']) + - chromium_linux.ChromiumLinuxPort.baseline_search_path(self)) - - def default_child_processes(self): - return 1 - - def path_to_test_expectations_file(self): - return self.path_from_webkit_base('LayoutTests', 'platform', - 'chromium-gpu', 'test_expectations.txt') - - def test_expectations_overrides(self): - return _gpu_overrides(self) - - -class ChromiumGpuMacPort(chromium_mac.ChromiumMacPort): - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'chromium-gpu-mac') - _set_gpu_options(kwargs.get('options')) - chromium_mac.ChromiumMacPort.__init__(self, **kwargs) - - def baseline_search_path(self): - return (map(self._webkit_baseline_path, ['chromium-gpu-mac', 'chromium-gpu']) + - chromium_mac.ChromiumMacPort.baseline_search_path(self)) - - def default_child_processes(self): - return 1 - - def path_to_test_expectations_file(self): - return self.path_from_webkit_base('LayoutTests', 'platform', - 'chromium-gpu', 'test_expectations.txt') - - def test_expectations_overrides(self): - return _gpu_overrides(self) - - -class ChromiumGpuWinPort(chromium_win.ChromiumWinPort): - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'chromium-gpu-win' + self.version()) - _set_gpu_options(kwargs.get('options')) - chromium_win.ChromiumWinPort.__init__(self, **kwargs) - - def baseline_search_path(self): - return (map(self._webkit_baseline_path, ['chromium-gpu-win', 'chromium-gpu']) + - chromium_win.ChromiumWinPort.baseline_search_path(self)) - - def default_child_processes(self): - return 1 - - def path_to_test_expectations_file(self): - return self.path_from_webkit_base('LayoutTests', 'platform', - 'chromium-gpu', 'test_expectations.txt') - - def test_expectations_overrides(self): - return _gpu_overrides(self) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py deleted file mode 100644 index 03bc98a..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_gpu_unittest.py +++ /dev/null @@ -1,75 +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. - -# 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 unittest - -from webkitpy.tool import mocktool -import chromium_gpu - - -class ChromiumGpuTest(unittest.TestCase): - def test_get_chromium_gpu_linux(self): - self.assertOverridesWorked('chromium-gpu-linux') - - def test_get_chromium_gpu_mac(self): - self.assertOverridesWorked('chromium-gpu-mac') - - def test_get_chromium_gpu_win(self): - self.assertOverridesWorked('chromium-gpu-win') - - def assertOverridesWorked(self, port_name): - # test that we got the right port - mock_options = mocktool.MockOptions(accelerated_compositing=None, - accelerated_2d_canvas=None, - builder_name='foo', - use_drt=None, - child_processes=None) - port = chromium_gpu.get(port_name=port_name, options=mock_options) - self.assertTrue(port._options.accelerated_compositing) - self.assertTrue(port._options.accelerated_2d_canvas) - self.assertTrue(port._options.use_drt) - self.assertEqual(port.default_child_processes(), 1) - self.assertEqual(port._options.builder_name, 'foo - GPU') - - # we use startswith() instead of Equal to gloss over platform versions. - self.assertTrue(port.name().startswith(port_name)) - - # test that it has the right directories in front of the search path. - paths = port.baseline_search_path() - self.assertEqual(port._webkit_baseline_path(port_name), paths[0]) - if port_name == 'chromium-gpu-linux': - self.assertEqual(port._webkit_baseline_path('chromium-gpu-win'), paths[1]) - self.assertEqual(port._webkit_baseline_path('chromium-gpu'), paths[2]) - else: - self.assertEqual(port._webkit_baseline_path('chromium-gpu'), paths[1]) - - # Test that we have the right expectations file. - self.assertTrue('chromium-gpu' in - port.path_to_test_expectations_file()) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py deleted file mode 100644 index b26a6b5..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py +++ /dev/null @@ -1,190 +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. - -"""Chromium Linux implementation of the Port interface.""" - -import logging -import os -import signal - -import chromium - -_log = logging.getLogger("webkitpy.layout_tests.port.chromium_linux") - - -class ChromiumLinuxPort(chromium.ChromiumPort): - """Chromium Linux implementation of the Port class.""" - - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'chromium-linux') - chromium.ChromiumPort.__init__(self, **kwargs) - - def baseline_search_path(self): - port_names = ["chromium-linux", "chromium-win", "chromium", "win", "mac"] - return map(self._webkit_baseline_path, port_names) - - def check_build(self, needs_http): - result = chromium.ChromiumPort.check_build(self, needs_http) - if needs_http: - if self.get_option('use_apache'): - result = self._check_apache_install() and result - else: - result = self._check_lighttpd_install() and result - result = self._check_wdiff_install() and result - - if not result: - _log.error('For complete Linux build requirements, please see:') - _log.error('') - _log.error(' http://code.google.com/p/chromium/wiki/' - 'LinuxBuildInstructions') - return result - - def test_platform_name(self): - # We use 'linux' instead of 'chromium-linux' in test_expectations.txt. - return 'linux' - - def version(self): - # We don't have different versions on linux. - return '' - - # - # PROTECTED METHODS - # - - def _build_path(self, *comps): - base = self.path_from_chromium_base() - if os.path.exists(os.path.join(base, 'sconsbuild')): - return os.path.join(base, 'sconsbuild', *comps) - if os.path.exists(os.path.join(base, 'out', *comps)) or not self.get_option('use_drt'): - return os.path.join(base, 'out', *comps) - base = self.path_from_webkit_base() - if os.path.exists(os.path.join(base, 'sconsbuild')): - return os.path.join(base, 'sconsbuild', *comps) - return os.path.join(base, 'out', *comps) - - def _check_apache_install(self): - result = chromium.check_file_exists(self._path_to_apache(), - "apache2") - result = chromium.check_file_exists(self._path_to_apache_config_file(), - "apache2 config file") and result - if not result: - _log.error(' Please install using: "sudo apt-get install ' - 'apache2 libapache2-mod-php5"') - _log.error('') - return result - - def _check_lighttpd_install(self): - result = chromium.check_file_exists( - self._path_to_lighttpd(), "LigHTTPd executable") - result = chromium.check_file_exists(self._path_to_lighttpd_php(), - "PHP CGI executable") and result - result = chromium.check_file_exists(self._path_to_lighttpd_modules(), - "LigHTTPd modules") and result - if not result: - _log.error(' Please install using: "sudo apt-get install ' - 'lighttpd php5-cgi"') - _log.error('') - return result - - def _check_wdiff_install(self): - result = chromium.check_file_exists(self._path_to_wdiff(), 'wdiff') - if not result: - _log.error(' Please install using: "sudo apt-get install ' - 'wdiff"') - _log.error('') - # FIXME: The ChromiumMac port always returns True. - return result - - def _path_to_apache(self): - if self._is_redhat_based(): - return '/usr/sbin/httpd' - else: - return '/usr/sbin/apache2' - - def _path_to_apache_config_file(self): - if self._is_redhat_based(): - config_name = 'fedora-httpd.conf' - else: - config_name = 'apache2-debian-httpd.conf' - - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - config_name) - - def _path_to_lighttpd(self): - return "/usr/sbin/lighttpd" - - def _path_to_lighttpd_modules(self): - return "/usr/lib/lighttpd" - - def _path_to_lighttpd_php(self): - return "/usr/bin/php-cgi" - - def _path_to_driver(self, configuration=None): - if not configuration: - configuration = self.get_option('configuration') - binary_name = 'test_shell' - if self.get_option('use_drt'): - binary_name = 'DumpRenderTree' - return self._build_path(configuration, binary_name) - - def _path_to_helper(self): - return None - - def _path_to_wdiff(self): - if self._is_redhat_based(): - return '/usr/bin/dwdiff' - else: - return '/usr/bin/wdiff' - - def _is_redhat_based(self): - return os.path.exists(os.path.join('/etc', 'redhat-release')) - - def _shut_down_http_server(self, server_pid): - """Shut down the lighttpd web server. Blocks until it's fully - shut down. - - Args: - server_pid: The process ID of the running server. - """ - # server_pid is not set when "http_server.py stop" is run manually. - if server_pid is None: - # TODO(mmoss) This isn't ideal, since it could conflict with - # lighttpd processes not started by http_server.py, - # but good enough for now. - self._executive.kill_all("lighttpd") - self._executive.kill_all("apache2") - else: - try: - os.kill(server_pid, signal.SIGTERM) - # TODO(mmoss) Maybe throw in a SIGKILL just to be sure? - except OSError: - # Sometimes we get a bad PID (e.g. from a stale httpd.pid - # file), so if kill fails on the given PID, just try to - # 'killall' web servers. - self._shut_down_http_server(None) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py deleted file mode 100644 index d1c383c..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py +++ /dev/null @@ -1,176 +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. - -"""Chromium Mac implementation of the Port interface.""" - -import logging -import os -import platform -import signal - -import chromium - -from webkitpy.common.system.executive import Executive - -_log = logging.getLogger("webkitpy.layout_tests.port.chromium_mac") - - -class ChromiumMacPort(chromium.ChromiumPort): - """Chromium Mac implementation of the Port class.""" - - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'chromium-mac') - chromium.ChromiumPort.__init__(self, **kwargs) - - def baseline_search_path(self): - port_names = ["chromium-mac", "chromium", "mac" + self.version(), "mac"] - return map(self._webkit_baseline_path, port_names) - - def check_build(self, needs_http): - result = chromium.ChromiumPort.check_build(self, needs_http) - result = self._check_wdiff_install() and result - if not result: - _log.error('For complete Mac build requirements, please see:') - _log.error('') - _log.error(' http://code.google.com/p/chromium/wiki/' - 'MacBuildInstructions') - return result - - def default_child_processes(self): - # FIXME: we need to run single-threaded for now. See - # https://bugs.webkit.org/show_bug.cgi?id=38553. Unfortunately this - # routine is called right before the logger is configured, so if we - # try to _log.warning(), it gets thrown away. - import sys - sys.stderr.write("Defaulting to one child - see https://bugs.webkit.org/show_bug.cgi?id=38553\n") - return 1 - - def driver_name(self): - """name for this port's equivalent of DumpRenderTree.""" - if self.get_option('use_drt'): - return "DumpRenderTree" - return "TestShell" - - def test_platform_name(self): - # We use 'mac' instead of 'chromium-mac' - return 'mac' - - def version(self): - # FIXME: It's strange that this string is -version, not just version. - os_version_string = platform.mac_ver()[0] # e.g. "10.5.6" - if not os_version_string: - return '-leopard' - release_version = int(os_version_string.split('.')[1]) - # we don't support 'tiger' or earlier releases - if release_version == 5: - return '-leopard' - elif release_version == 6: - return '-snowleopard' - return '' - - # - # PROTECTED METHODS - # - - def _build_path(self, *comps): - path = self.path_from_chromium_base('xcodebuild', *comps) - if os.path.exists(path) or not self.get_option('use_drt'): - return path - return self.path_from_webkit_base('WebKit', 'chromium', 'xcodebuild', - *comps) - - def _check_wdiff_install(self): - try: - # We're ignoring the return and always returning True - self._executive.run_command([self._path_to_wdiff()], error_handler=Executive.ignore_error) - except OSError: - _log.warning('wdiff not found. Install using MacPorts or some ' - 'other means') - return True - - def _lighttpd_path(self, *comps): - return self.path_from_chromium_base('third_party', 'lighttpd', - 'mac', *comps) - - def _path_to_apache(self): - return '/usr/sbin/httpd' - - def _path_to_apache_config_file(self): - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'apache2-httpd.conf') - - def _path_to_lighttpd(self): - return self._lighttpd_path('bin', 'lighttpd') - - def _path_to_lighttpd_modules(self): - return self._lighttpd_path('lib') - - def _path_to_lighttpd_php(self): - return self._lighttpd_path('bin', 'php-cgi') - - def _path_to_driver(self, configuration=None): - # FIXME: make |configuration| happy with case-sensitive file - # systems. - if not configuration: - configuration = self.get_option('configuration') - return self._build_path(configuration, self.driver_name() + '.app', - 'Contents', 'MacOS', self.driver_name()) - - def _path_to_helper(self): - binary_name = 'layout_test_helper' - if self.get_option('use_drt'): - binary_name = 'LayoutTestHelper' - return self._build_path(self.get_option('configuration'), binary_name) - - def _path_to_wdiff(self): - return 'wdiff' - - def _shut_down_http_server(self, server_pid): - """Shut down the lighttpd web server. Blocks until it's fully - shut down. - - Args: - server_pid: The process ID of the running server. - """ - # server_pid is not set when "http_server.py stop" is run manually. - if server_pid is None: - # TODO(mmoss) This isn't ideal, since it could conflict with - # lighttpd processes not started by http_server.py, - # but good enough for now. - self._executive.kill_all('lighttpd') - self._executive.kill_all('httpd') - else: - try: - os.kill(server_pid, signal.SIGTERM) - # TODO(mmoss) Maybe throw in a SIGKILL just to be sure? - except OSError: - # Sometimes we get a bad PID (e.g. from a stale httpd.pid - # file), so if kill fails on the given PID, just try to - # 'killall' web servers. - self._shut_down_http_server(None) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py deleted file mode 100644 index d63faa0..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac_unittest.py +++ /dev/null @@ -1,40 +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 chromium_mac -import unittest - -from webkitpy.thirdparty.mock import Mock - - -class ChromiumMacPortTest(unittest.TestCase): - - def test_check_wdiff_install(self): - port = chromium_mac.ChromiumMacPort() - # Currently is always true, just logs if missing. - self.assertTrue(port._check_wdiff_install()) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py deleted file mode 100644 index 5396522..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py +++ /dev/null @@ -1,186 +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 unittest -import StringIO - -from webkitpy.tool import mocktool -from webkitpy.thirdparty.mock import Mock - -import chromium -import chromium_linux -import chromium_mac -import chromium_win - -class ChromiumDriverTest(unittest.TestCase): - - def setUp(self): - mock_port = Mock() - mock_port.get_option = lambda option_name: '' - self.driver = chromium.ChromiumDriver(mock_port, worker_number=0) - - def test_test_shell_command(self): - expected_command = "test.html 2 checksum\n" - self.assertEqual(self.driver._test_shell_command("test.html", 2, "checksum"), expected_command) - - def _assert_write_command_and_read_line(self, input=None, expected_line=None, expected_stdin=None, expected_crash=False): - if not expected_stdin: - if input: - expected_stdin = input - else: - # We reset stdin, so we should expect stdin.getValue = "" - expected_stdin = "" - self.driver._proc.stdin = StringIO.StringIO() - line, did_crash = self.driver._write_command_and_read_line(input) - self.assertEqual(self.driver._proc.stdin.getvalue(), expected_stdin) - self.assertEqual(line, expected_line) - self.assertEqual(did_crash, expected_crash) - - def test_write_command_and_read_line(self): - self.driver._proc = Mock() - # Set up to read 3 lines before we get an IOError - self.driver._proc.stdout = StringIO.StringIO("first\nsecond\nthird\n") - - unicode_input = u"I \u2661 Unicode" - utf8_input = unicode_input.encode("utf-8") - # Test unicode input conversion to utf-8 - self._assert_write_command_and_read_line(input=unicode_input, expected_stdin=utf8_input, expected_line="first\n") - # Test str() input. - self._assert_write_command_and_read_line(input="foo", expected_line="second\n") - # Test input=None - self._assert_write_command_and_read_line(expected_line="third\n") - # Test reading from a closed/empty stream. - # reading from a StringIO does not raise IOError like a real file would, so raise IOError manually. - def mock_readline(): - raise IOError - self.driver._proc.stdout.readline = mock_readline - self._assert_write_command_and_read_line(expected_crash=True) - - -class ChromiumPortTest(unittest.TestCase): - class TestMacPort(chromium_mac.ChromiumMacPort): - def __init__(self, options): - chromium_mac.ChromiumMacPort.__init__(self, - port_name='test-port', - options=options) - - def default_configuration(self): - self.default_configuration_called = True - return 'default' - - class TestLinuxPort(chromium_linux.ChromiumLinuxPort): - def __init__(self, options): - chromium_linux.ChromiumLinuxPort.__init__(self, - port_name='test-port', - options=options) - - def default_configuration(self): - self.default_configuration_called = True - return 'default' - - def test_path_to_image_diff(self): - mock_options = mocktool.MockOptions(use_drt=True) - port = ChromiumPortTest.TestLinuxPort(options=mock_options) - self.assertTrue(port._path_to_image_diff().endswith( - '/out/default/ImageDiff'), msg=port._path_to_image_diff()) - port = ChromiumPortTest.TestMacPort(options=mock_options) - self.assertTrue(port._path_to_image_diff().endswith( - '/xcodebuild/default/ImageDiff')) - # FIXME: Figure out how this is going to work on Windows. - #port = chromium_win.ChromiumWinPort('test-port', options=MockOptions()) - - def test_skipped_layout_tests(self): - mock_options = mocktool.MockOptions(use_drt=True) - port = ChromiumPortTest.TestLinuxPort(options=mock_options) - - fake_test = os.path.join(port.layout_tests_dir(), "fast/js/not-good.js") - - port.test_expectations = lambda: """BUG_TEST SKIP : fast/js/not-good.js = TEXT -LINUX WIN : fast/js/very-good.js = TIMEOUT PASS""" - port.test_expectations_overrides = lambda: '' - port.tests = lambda paths: set() - port.path_exists = lambda test: True - - skipped_tests = port.skipped_layout_tests(extra_test_files=[fake_test, ]) - self.assertTrue("fast/js/not-good.js" in skipped_tests) - - def test_default_configuration(self): - mock_options = mocktool.MockOptions() - port = ChromiumPortTest.TestLinuxPort(options=mock_options) - self.assertEquals(mock_options.configuration, 'default') - self.assertTrue(port.default_configuration_called) - - mock_options = mocktool.MockOptions(configuration=None) - port = ChromiumPortTest.TestLinuxPort(mock_options) - self.assertEquals(mock_options.configuration, 'default') - self.assertTrue(port.default_configuration_called) - - def test_diff_image(self): - class TestPort(ChromiumPortTest.TestLinuxPort): - def _path_to_image_diff(self): - return "/path/to/image_diff" - - class MockExecute: - def __init__(self, result): - self._result = result - - def run_command(self, - args, - cwd=None, - input=None, - error_handler=None, - return_exit_code=False, - return_stderr=True, - decode_output=False): - if return_exit_code: - return self._result - return '' - - mock_options = mocktool.MockOptions(use_drt=False) - port = ChromiumPortTest.TestLinuxPort(mock_options) - - # Images are different. - port._executive = MockExecute(0) - self.assertEquals(False, port.diff_image("EXPECTED", "ACTUAL")) - - # Images are the same. - port._executive = MockExecute(1) - self.assertEquals(True, port.diff_image("EXPECTED", "ACTUAL")) - - # There was some error running image_diff. - port._executive = MockExecute(2) - exception_raised = False - try: - port.diff_image("EXPECTED", "ACTUAL") - except ValueError, e: - exception_raised = True - self.assertFalse(exception_raised) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py deleted file mode 100644 index 69b529a..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py +++ /dev/null @@ -1,174 +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. - -"""Chromium Win implementation of the Port interface.""" - -import logging -import os -import sys - -import chromium - -_log = logging.getLogger("webkitpy.layout_tests.port.chromium_win") - - -class ChromiumWinPort(chromium.ChromiumPort): - """Chromium Win implementation of the Port class.""" - - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'chromium-win' + self.version()) - chromium.ChromiumPort.__init__(self, **kwargs) - - def setup_environ_for_server(self): - env = chromium.ChromiumPort.setup_environ_for_server(self) - # Put the cygwin directory first in the path to find cygwin1.dll. - env["PATH"] = "%s;%s" % ( - self.path_from_chromium_base("third_party", "cygwin", "bin"), - env["PATH"]) - # Configure the cygwin directory so that pywebsocket finds proper - # python executable to run cgi program. - env["CYGWIN_PATH"] = self.path_from_chromium_base( - "third_party", "cygwin", "bin") - if (sys.platform == "win32" and self.get_option('register_cygwin')): - setup_mount = self.path_from_chromium_base("third_party", - "cygwin", - "setup_mount.bat") - self._executive.run_command([setup_mount]) - return env - - def baseline_search_path(self): - port_names = [] - if self._name.endswith('-win-xp'): - port_names.append("chromium-win-xp") - if self._name.endswith('-win-xp') or self._name.endswith('-win-vista'): - port_names.append("chromium-win-vista") - # FIXME: This may need to include mac-snowleopard like win.py. - port_names.extend(["chromium-win", "chromium", "win", "mac"]) - return map(self._webkit_baseline_path, port_names) - - def check_build(self, needs_http): - result = chromium.ChromiumPort.check_build(self, needs_http) - if not result: - _log.error('For complete Windows build requirements, please ' - 'see:') - _log.error('') - _log.error(' http://dev.chromium.org/developers/how-tos/' - 'build-instructions-windows') - return result - - def relative_test_filename(self, filename): - path = filename[len(self.layout_tests_dir()) + 1:] - return path.replace('\\', '/') - - def test_platform_name(self): - # We return 'win-xp', not 'chromium-win-xp' here, for convenience. - return 'win' + self.version() - - def version(self): - if not hasattr(sys, 'getwindowsversion'): - return '' - winver = sys.getwindowsversion() - if winver[0] == 6 and (winver[1] == 1): - return '-7' - if winver[0] == 6 and (winver[1] == 0): - return '-vista' - if winver[0] == 5 and (winver[1] == 1 or winver[1] == 2): - return '-xp' - return '' - - # - # PROTECTED ROUTINES - # - - def _build_path(self, *comps): - p = self.path_from_chromium_base('webkit', *comps) - if os.path.exists(p): - return p - p = self.path_from_chromium_base('chrome', *comps) - if os.path.exists(p) or not self.get_option('use_drt'): - return p - return os.path.join(self.path_from_webkit_base(), 'WebKit', 'chromium', - *comps) - - def _lighttpd_path(self, *comps): - return self.path_from_chromium_base('third_party', 'lighttpd', 'win', - *comps) - - def _path_to_apache(self): - return self.path_from_chromium_base('third_party', 'cygwin', 'usr', - 'sbin', 'httpd') - - def _path_to_apache_config_file(self): - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'cygwin-httpd.conf') - - def _path_to_lighttpd(self): - return self._lighttpd_path('LightTPD.exe') - - def _path_to_lighttpd_modules(self): - return self._lighttpd_path('lib') - - def _path_to_lighttpd_php(self): - return self._lighttpd_path('php5', 'php-cgi.exe') - - def _path_to_driver(self, configuration=None): - if not configuration: - configuration = self.get_option('configuration') - binary_name = 'test_shell.exe' - if self.get_option('use_drt'): - binary_name = 'DumpRenderTree.exe' - return self._build_path(configuration, binary_name) - - def _path_to_helper(self): - binary_name = 'layout_test_helper.exe' - if self.get_option('use_drt'): - binary_name = 'LayoutTestHelper.exe' - return self._build_path(self.get_option('configuration'), binary_name) - - def _path_to_image_diff(self): - binary_name = 'image_diff.exe' - if self.get_option('use_drt'): - binary_name = 'ImageDiff.exe' - return self._build_path(self.get_option('configuration'), binary_name) - - def _path_to_wdiff(self): - return self.path_from_chromium_base('third_party', 'cygwin', 'bin', - 'wdiff.exe') - - def _shut_down_http_server(self, server_pid): - """Shut down the lighttpd web server. Blocks until it's fully - shut down. - - Args: - server_pid: The process ID of the running server. - """ - # FIXME: Why are we ignoring server_pid and calling - # _kill_all instead of Executive.kill_process(pid)? - self._executive.kill_all("LightTPD.exe") - self._executive.kill_all("httpd.exe") diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py deleted file mode 100644 index 36f3c6b..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win_unittest.py +++ /dev/null @@ -1,74 +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 sys -import unittest -import chromium_win -from webkitpy.common.system import outputcapture -from webkitpy.tool import mocktool - - -class ChromiumWinTest(unittest.TestCase): - - class RegisterCygwinOption(object): - def __init__(self): - self.register_cygwin = True - - def setUp(self): - self.orig_platform = sys.platform - - def tearDown(self): - sys.platform = self.orig_platform - - def _mock_path_from_chromium_base(self, *comps): - return os.path.join("/chromium/src", *comps) - - def test_setup_environ_for_server(self): - port = chromium_win.ChromiumWinPort() - port._executive = mocktool.MockExecutive(should_log=True) - port.path_from_chromium_base = self._mock_path_from_chromium_base - output = outputcapture.OutputCapture() - orig_environ = os.environ.copy() - env = output.assert_outputs(self, port.setup_environ_for_server) - self.assertEqual(orig_environ["PATH"], os.environ["PATH"]) - self.assertNotEqual(env["PATH"], os.environ["PATH"]) - - def test_setup_environ_for_server_register_cygwin(self): - sys.platform = "win32" - port = chromium_win.ChromiumWinPort( - options=ChromiumWinTest.RegisterCygwinOption()) - port._executive = mocktool.MockExecutive(should_log=True) - port.path_from_chromium_base = self._mock_path_from_chromium_base - setup_mount = self._mock_path_from_chromium_base("third_party", - "cygwin", - "setup_mount.bat") - expected_stderr = "MOCK run_command: %s\n" % [setup_mount] - output = outputcapture.OutputCapture() - output.assert_outputs(self, port.setup_environ_for_server, - expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/config.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/config.py deleted file mode 100644 index 9aec637..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/config.py +++ /dev/null @@ -1,169 +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. - -"""Wrapper objects for WebKit-specific utility routines.""" - -# FIXME: This file needs to be unified with common/checkout/scm.py and -# common/config/ports.py . - -import os - -from webkitpy.common.system import logutils -from webkitpy.common.system import executive - - -_log = logutils.get_logger(__file__) - -# -# FIXME: This is used to record if we've already hit the filesystem to look -# for a default configuration. We cache this to speed up the unit tests, -# but this can be reset with clear_cached_configuration(). This should be -# replaced with us consistently using MockConfigs() for tests that don't -# hit the filesystem at all and provide a reliable value. -# -_have_determined_configuration = False -_configuration = "Release" - - -def clear_cached_configuration(): - global _have_determined_configuration, _configuration - _have_determined_configuration = False - _configuration = "Release" - - -class Config(object): - _FLAGS_FROM_CONFIGURATIONS = { - "Debug": "--debug", - "Release": "--release", - } - - def __init__(self, executive, filesystem): - self._executive = executive - self._filesystem = filesystem - self._webkit_base_dir = None - self._default_configuration = None - self._build_directories = {} - - def build_directory(self, configuration): - """Returns the path to the build directory for the configuration.""" - if configuration: - flags = ["--configuration", - self._FLAGS_FROM_CONFIGURATIONS[configuration]] - else: - configuration = "" - flags = ["--top-level"] - - if not self._build_directories.get(configuration): - args = ["perl", self._script_path("webkit-build-directory")] + flags - self._build_directories[configuration] = ( - self._executive.run_command(args).rstrip()) - - return self._build_directories[configuration] - - def build_dumprendertree(self, configuration): - """Builds DRT in the given configuration. - - Returns True if the build was successful and up-to-date.""" - flag = self._FLAGS_FROM_CONFIGURATIONS[configuration] - exit_code = self._executive.run_command([ - self._script_path("build-dumprendertree"), flag], - return_exit_code=True) - if exit_code != 0: - _log.error("Failed to build DumpRenderTree") - return False - return True - - def default_configuration(self): - """Returns the default configuration for the user. - - Returns the value set by 'set-webkit-configuration', or "Release" - if that has not been set. This mirrors the logic in webkitdirs.pm.""" - if not self._default_configuration: - self._default_configuration = self._determine_configuration() - if not self._default_configuration: - self._default_configuration = 'Release' - if self._default_configuration not in self._FLAGS_FROM_CONFIGURATIONS: - _log.warn("Configuration \"%s\" is not a recognized value.\n" % - self._default_configuration) - _log.warn("Scripts may fail. " - "See 'set-webkit-configuration --help'.") - return self._default_configuration - - def path_from_webkit_base(self, *comps): - return self._filesystem.join(self.webkit_base_dir(), *comps) - - def webkit_base_dir(self): - """Returns the absolute path to the top of the WebKit tree. - - Raises an AssertionError if the top dir can't be determined.""" - # Note: this code somewhat duplicates the code in - # scm.find_checkout_root(). However, that code only works if the top - # of the SCM repository also matches the top of the WebKit tree. The - # Chromium ports, for example, only check out subdirectories like - # WebKitTools/Scripts, and so we still have to do additional work - # to find the top of the tree. - # - # This code will also work if there is no SCM system at all. - if not self._webkit_base_dir: - abspath = os.path.abspath(__file__) - self._webkit_base_dir = abspath[0:abspath.find('WebKitTools')] - return self._webkit_base_dir - - def _script_path(self, script_name): - return self._filesystem.join(self.webkit_base_dir(), "WebKitTools", - "Scripts", script_name) - - def _determine_configuration(self): - # This mirrors the logic in webkitdirs.pm:determineConfiguration(). - # - # FIXME: See the comment at the top of the file regarding unit tests - # and our use of global mutable static variables. - global _have_determined_configuration, _configuration - if not _have_determined_configuration: - contents = self._read_configuration() - if not contents: - contents = "Release" - if contents == "Deployment": - contents = "Release" - if contents == "Development": - contents = "Debug" - _configuration = contents - _have_determined_configuration = True - return _configuration - - def _read_configuration(self): - try: - configuration_path = self._filesystem.join(self.build_directory(None), - "Configuration") - if not self._filesystem.exists(configuration_path): - return None - except (OSError, executive.ScriptError): - return None - - return self._filesystem.read_text_file(configuration_path).rstrip() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/config_mock.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/config_mock.py deleted file mode 100644 index af71fa3..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/config_mock.py +++ /dev/null @@ -1,50 +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. - -"""Wrapper objects for WebKit-specific utility routines.""" - - -class MockConfig(object): - def __init__(self, default_configuration='Release'): - self._default_configuration = default_configuration - - def build_directory(self, configuration): - return "/build" - - def build_dumprendertree(self, configuration): - return True - - def default_configuration(self): - return self._default_configuration - - def path_from_webkit_base(self, *comps): - return "/" + "/".join(list(comps)) - - def webkit_base_dir(self): - return "/" diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/config_standalone.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/config_standalone.py deleted file mode 100644 index 3dec3b9..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/config_standalone.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. - -"""FIXME: This script is used by -config_unittest.test_default_configuration__standalone() to read the -default configuration to work around any possible caching / reset bugs. See -https://bugs.webkit.org/show_bug?id=49360 for the motivation. We can remove -this test when we remove the global configuration cache in config.py.""" - -import os -import unittest -import sys - - -# Ensure that webkitpy is in PYTHONPATH. -this_dir = os.path.abspath(sys.path[0]) -up = os.path.dirname -script_dir = up(up(up(this_dir))) -if script_dir not in sys.path: - sys.path.append(script_dir) - -from webkitpy.common.system import executive -from webkitpy.common.system import executive_mock -from webkitpy.common.system import filesystem -from webkitpy.common.system import filesystem_mock - -import config - - -def main(argv=None): - if not argv: - argv = sys.argv - - if len(argv) == 3 and argv[1] == '--mock': - e = executive_mock.MockExecutive2(output='foo') - fs = filesystem_mock.MockFileSystem({'foo/Configuration': argv[2]}) - else: - e = executive.Executive() - fs = filesystem.FileSystem() - - c = config.Config(e, fs) - print c.default_configuration() - -if __name__ == '__main__': - main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/config_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/config_unittest.py deleted file mode 100644 index 2d23691..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/config_unittest.py +++ /dev/null @@ -1,201 +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 sys -import unittest - -from webkitpy.common.system import executive -from webkitpy.common.system import executive_mock -from webkitpy.common.system import filesystem -from webkitpy.common.system import filesystem_mock -from webkitpy.common.system import outputcapture - -import config - - -def mock_run_command(arg_list): - # Set this to True to test actual output (where possible). - integration_test = False - if integration_test: - return executive.Executive().run_command(arg_list) - - if 'webkit-build-directory' in arg_list[1]: - return mock_webkit_build_directory(arg_list[2:]) - return 'Error' - - -def mock_webkit_build_directory(arg_list): - if arg_list == ['--top-level']: - return '/WebKitBuild' - elif arg_list == ['--configuration', '--debug']: - return '/WebKitBuild/Debug' - elif arg_list == ['--configuration', '--release']: - return '/WebKitBuild/Release' - return 'Error' - - -class ConfigTest(unittest.TestCase): - def tearDown(self): - config.clear_cached_configuration() - - def make_config(self, output='', files={}, exit_code=0, exception=None, - run_command_fn=None): - e = executive_mock.MockExecutive2(output=output, exit_code=exit_code, - exception=exception, - run_command_fn=run_command_fn) - fs = filesystem_mock.MockFileSystem(files) - return config.Config(e, fs) - - def assert_configuration(self, contents, expected): - # This tests that a configuration file containing - # _contents_ ends up being interpreted as _expected_. - c = self.make_config('foo', {'foo/Configuration': contents}) - self.assertEqual(c.default_configuration(), expected) - - def test_build_directory(self): - # --top-level - c = self.make_config(run_command_fn=mock_run_command) - self.assertTrue(c.build_directory(None).endswith('WebKitBuild')) - - # Test again to check caching - self.assertTrue(c.build_directory(None).endswith('WebKitBuild')) - - # Test other values - self.assertTrue(c.build_directory('Release').endswith('/Release')) - self.assertTrue(c.build_directory('Debug').endswith('/Debug')) - self.assertRaises(KeyError, c.build_directory, 'Unknown') - - def test_build_dumprendertree__success(self): - c = self.make_config(exit_code=0) - self.assertTrue(c.build_dumprendertree("Debug")) - self.assertTrue(c.build_dumprendertree("Release")) - self.assertRaises(KeyError, c.build_dumprendertree, "Unknown") - - def test_build_dumprendertree__failure(self): - c = self.make_config(exit_code=-1) - - # FIXME: Build failures should log errors. However, the message we - # get depends on how we're being called; as a standalone test, - # we'll get the "no handlers found" message. As part of - # test-webkitpy, we get the actual message. Really, we need - # outputcapture to install its own handler. - oc = outputcapture.OutputCapture() - oc.capture_output() - self.assertFalse(c.build_dumprendertree('Debug')) - oc.restore_output() - - oc.capture_output() - self.assertFalse(c.build_dumprendertree('Release')) - oc.restore_output() - - def test_default_configuration__release(self): - self.assert_configuration('Release', 'Release') - - def test_default_configuration__debug(self): - self.assert_configuration('Debug', 'Debug') - - def test_default_configuration__deployment(self): - self.assert_configuration('Deployment', 'Release') - - def test_default_configuration__development(self): - self.assert_configuration('Development', 'Debug') - - def test_default_configuration__notfound(self): - # This tests what happens if the default configuration file - # doesn't exist. - c = self.make_config(output='foo', files={'foo/Configuration': None}) - self.assertEqual(c.default_configuration(), "Release") - - def test_default_configuration__unknown(self): - # Ignore the warning about an unknown configuration value. - oc = outputcapture.OutputCapture() - oc.capture_output() - self.assert_configuration('Unknown', 'Unknown') - oc.restore_output() - - def test_default_configuration__standalone(self): - # FIXME: This test runs a standalone python script to test - # reading the default configuration to work around any possible - # caching / reset bugs. See https://bugs.webkit.org/show_bug?id=49360 - # for the motivation. We can remove this test when we remove the - # global configuration cache in config.py. - e = executive.Executive() - fs = filesystem.FileSystem() - c = config.Config(e, fs) - script = c.path_from_webkit_base('WebKitTools', 'Scripts', - 'webkitpy', 'layout_tests', 'port', 'config_standalone.py') - - # Note: don't use 'Release' here, since that's the normal default. - expected = 'Debug' - - args = [sys.executable, script, '--mock', expected] - actual = e.run_command(args).rstrip() - self.assertEqual(actual, expected) - - def test_default_configuration__no_perl(self): - # We need perl to run webkit-build-directory to find out where the - # default configuration file is. See what happens if perl isn't - # installed. (We should get the default value, 'Release'). - c = self.make_config(exception=OSError) - actual = c.default_configuration() - self.assertEqual(actual, 'Release') - - def test_default_configuration__scripterror(self): - # We run webkit-build-directory to find out where the default - # configuration file is. See what happens if that script fails. - # (We should get the default value, 'Release'). - c = self.make_config(exception=executive.ScriptError()) - actual = c.default_configuration() - self.assertEqual(actual, 'Release') - - def test_path_from_webkit_base(self): - # FIXME: We use a real filesystem here. Should this move to a - # mocked one? - c = config.Config(executive.Executive(), filesystem.FileSystem()) - self.assertTrue(c.path_from_webkit_base('foo')) - - def test_webkit_base_dir(self): - # FIXME: We use a real filesystem here. Should this move to a - # mocked one? - c = config.Config(executive.Executive(), filesystem.FileSystem()) - base_dir = c.webkit_base_dir() - self.assertTrue(base_dir) - - orig_cwd = os.getcwd() - os.chdir(os.environ['HOME']) - c = config.Config(executive.Executive(), filesystem.FileSystem()) - try: - base_dir_2 = c.webkit_base_dir() - self.assertEqual(base_dir, base_dir_2) - finally: - os.chdir(orig_cwd) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py deleted file mode 100644 index 4ed34e6..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/dryrun.py +++ /dev/null @@ -1,132 +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 Google name 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. - -"""This is a test implementation of the Port interface that generates the - correct output for every test. It can be used for perf testing, because - it is pretty much a lower limit on how fast a port can possibly run. - - This implementation acts as a wrapper around a real port (the real port - is held as a delegate object). To specify which port, use the port name - 'dryrun-XXX' (e.g., 'dryrun-chromium-mac-leopard'). If you use just - 'dryrun', it uses the default port. - - Note that because this is really acting as a wrapper around the underlying - port, you must be able to run the underlying port as well - (check_build() and check_sys_deps() must pass and auxiliary binaries - like layout_test_helper and httpd must work). - - This implementation also modifies the test expectations so that all - tests are either SKIPPED or expected to PASS.""" - -from __future__ import with_statement - -import os -import sys -import time - -from webkitpy.layout_tests.layout_package import test_output - -import base -import factory - - -class DryRunPort(object): - """DryRun implementation of the Port interface.""" - - def __init__(self, **kwargs): - pfx = 'dryrun-' - if 'port_name' in kwargs: - if kwargs['port_name'].startswith(pfx): - kwargs['port_name'] = kwargs['port_name'][len(pfx):] - else: - kwargs['port_name'] = None - self.__delegate = factory.get(**kwargs) - - def __getattr__(self, name): - return getattr(self.__delegate, name) - - def check_build(self, needs_http): - return True - - def check_sys_deps(self, needs_http): - return True - - def start_helper(self): - pass - - def start_http_server(self): - pass - - def start_websocket_server(self): - pass - - def stop_helper(self): - pass - - def stop_http_server(self): - pass - - def stop_websocket_server(self): - pass - - def create_driver(self, worker_number): - return DryrunDriver(self, worker_number) - - -class DryrunDriver(base.Driver): - """Dryrun implementation of the DumpRenderTree / Driver interface.""" - - def __init__(self, port, worker_number): - self._port = port - self._worker_number = worker_number - - def cmd_line(self): - return ['None'] - - def poll(self): - return None - - def run_test(self, test_input): - start_time = time.time() - text_output = self._port.expected_text(test_input.filename) - - if test_input.image_hash is not None: - image = self._port.expected_image(test_input.filename) - hash = self._port.expected_checksum(test_input.filename) - else: - image = None - hash = None - return test_output.TestOutput(text_output, image, hash, False, - time.time() - start_time, False, None) - - def start(self): - pass - - def stop(self): - pass diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py deleted file mode 100644 index 6935744..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory.py +++ /dev/null @@ -1,113 +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. - -"""Factory method to retrieve the appropriate port implementation.""" - - -import sys - -ALL_PORT_NAMES = ['test', 'dryrun', 'mac', 'win', 'gtk', 'qt', 'chromium-mac', - 'chromium-linux', 'chromium-win', 'google-chrome-win', - 'google-chrome-mac', 'google-chrome-linux32', 'google-chrome-linux64'] - - -def get(port_name=None, options=None, **kwargs): - """Returns an object implementing the Port interface. If - port_name is None, this routine attempts to guess at the most - appropriate port on this platform.""" - # Wrapped for backwards-compatibility - if port_name: - kwargs['port_name'] = port_name - if options: - kwargs['options'] = options - return _get_kwargs(**kwargs) - - -def _get_kwargs(**kwargs): - port_to_use = kwargs.get('port_name', None) - options = kwargs.get('options', None) - if port_to_use is None: - if sys.platform == 'win32' or sys.platform == 'cygwin': - if options and hasattr(options, 'chromium') and options.chromium: - port_to_use = 'chromium-win' - else: - port_to_use = 'win' - elif sys.platform == 'linux2': - port_to_use = 'chromium-linux' - elif sys.platform == 'darwin': - if options and hasattr(options, 'chromium') and options.chromium: - port_to_use = 'chromium-mac' - else: - port_to_use = 'mac' - - if port_to_use is None: - raise NotImplementedError('unknown port; sys.platform = "%s"' % - sys.platform) - - if port_to_use == 'test': - import test - maker = test.TestPort - elif port_to_use.startswith('dryrun'): - import dryrun - maker = dryrun.DryRunPort - elif port_to_use.startswith('mac'): - import mac - maker = mac.MacPort - elif port_to_use.startswith('win'): - import win - maker = win.WinPort - elif port_to_use.startswith('gtk'): - import gtk - maker = gtk.GtkPort - elif port_to_use.startswith('qt'): - import qt - maker = qt.QtPort - elif port_to_use.startswith('chromium-gpu'): - import chromium_gpu - maker = chromium_gpu.get - elif port_to_use.startswith('chromium-mac'): - import chromium_mac - maker = chromium_mac.ChromiumMacPort - elif port_to_use.startswith('chromium-linux'): - import chromium_linux - maker = chromium_linux.ChromiumLinuxPort - elif port_to_use.startswith('chromium-win'): - import chromium_win - maker = chromium_win.ChromiumWinPort - elif port_to_use.startswith('google-chrome'): - import google_chrome - maker = google_chrome.GetGoogleChromePort - else: - raise NotImplementedError('unsupported port: %s' % port_to_use) - return maker(**kwargs) - -def get_all(options=None): - """Returns all the objects implementing the Port interface.""" - return dict([(port_name, get(port_name, options=options)) - for port_name in ALL_PORT_NAMES]) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py deleted file mode 100644 index 978a557..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/factory_unittest.py +++ /dev/null @@ -1,188 +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 sys -import unittest - -from webkitpy.tool import mocktool - -import chromium_gpu -import chromium_linux -import chromium_mac -import chromium_win -import dryrun -import factory -import google_chrome -import gtk -import mac -import qt -import test -import win - - -class FactoryTest(unittest.TestCase): - """Test factory creates proper port object for the target. - - Target is specified by port_name, sys.platform and options. - - """ - # FIXME: The ports themselves should expose what options they require, - # instead of passing generic "options". - - def setUp(self): - self.real_sys_platform = sys.platform - self.webkit_options = mocktool.MockOptions(pixel_tests=False) - self.chromium_options = mocktool.MockOptions(pixel_tests=False, - chromium=True) - - def tearDown(self): - sys.platform = self.real_sys_platform - - def assert_port(self, port_name, expected_port, port_obj=None): - """Helper assert for port_name. - - Args: - port_name: port name to get port object. - expected_port: class of expected port object. - port_obj: optional port object - """ - port_obj = port_obj or factory.get(port_name=port_name) - self.assertTrue(isinstance(port_obj, expected_port)) - - def assert_platform_port(self, platform, options, expected_port): - """Helper assert for platform and options. - - Args: - platform: sys.platform. - options: options to get port object. - expected_port: class of expected port object. - - """ - orig_platform = sys.platform - sys.platform = platform - self.assertTrue(isinstance(factory.get(options=options), - expected_port)) - sys.platform = orig_platform - - def test_test(self): - self.assert_port("test", test.TestPort) - - def test_dryrun(self): - self.assert_port("dryrun-test", dryrun.DryRunPort) - self.assert_port("dryrun-mac", dryrun.DryRunPort) - - def test_mac(self): - self.assert_port("mac", mac.MacPort) - self.assert_platform_port("darwin", None, mac.MacPort) - self.assert_platform_port("darwin", self.webkit_options, mac.MacPort) - - def test_win(self): - self.assert_port("win", win.WinPort) - self.assert_platform_port("win32", None, win.WinPort) - self.assert_platform_port("win32", self.webkit_options, win.WinPort) - self.assert_platform_port("cygwin", None, win.WinPort) - self.assert_platform_port("cygwin", self.webkit_options, win.WinPort) - - def test_google_chrome(self): - # The actual Chrome class names aren't available so we test that the - # objects we get are at least subclasses of the Chromium versions. - self.assert_port("google-chrome-linux32", - chromium_linux.ChromiumLinuxPort) - self.assert_port("google-chrome-linux64", - chromium_linux.ChromiumLinuxPort) - self.assert_port("google-chrome-win", - chromium_win.ChromiumWinPort) - self.assert_port("google-chrome-mac", - chromium_mac.ChromiumMacPort) - - def test_gtk(self): - self.assert_port("gtk", gtk.GtkPort) - - def test_qt(self): - self.assert_port("qt", qt.QtPort) - - def test_chromium_gpu_linux(self): - self.assert_port("chromium-gpu-linux", chromium_gpu.ChromiumGpuLinuxPort) - - def test_chromium_gpu_mac(self): - self.assert_port("chromium-gpu-mac", chromium_gpu.ChromiumGpuMacPort) - - def test_chromium_gpu_win(self): - self.assert_port("chromium-gpu-win", chromium_gpu.ChromiumGpuWinPort) - - def test_chromium_mac(self): - self.assert_port("chromium-mac", chromium_mac.ChromiumMacPort) - self.assert_platform_port("darwin", self.chromium_options, - chromium_mac.ChromiumMacPort) - - def test_chromium_linux(self): - self.assert_port("chromium-linux", chromium_linux.ChromiumLinuxPort) - self.assert_platform_port("linux2", self.chromium_options, - chromium_linux.ChromiumLinuxPort) - - def test_chromium_win(self): - self.assert_port("chromium-win", chromium_win.ChromiumWinPort) - self.assert_platform_port("win32", self.chromium_options, - chromium_win.ChromiumWinPort) - self.assert_platform_port("cygwin", self.chromium_options, - chromium_win.ChromiumWinPort) - - def test_get_all_ports(self): - ports = factory.get_all() - for name in factory.ALL_PORT_NAMES: - self.assertTrue(name in ports.keys()) - self.assert_port("test", test.TestPort, ports["test"]) - self.assert_port("dryrun-test", dryrun.DryRunPort, ports["dryrun"]) - self.assert_port("dryrun-mac", dryrun.DryRunPort, ports["dryrun"]) - self.assert_port("mac", mac.MacPort, ports["mac"]) - self.assert_port("win", win.WinPort, ports["win"]) - self.assert_port("gtk", gtk.GtkPort, ports["gtk"]) - self.assert_port("qt", qt.QtPort, ports["qt"]) - self.assert_port("chromium-mac", chromium_mac.ChromiumMacPort, - ports["chromium-mac"]) - self.assert_port("chromium-linux", chromium_linux.ChromiumLinuxPort, - ports["chromium-linux"]) - self.assert_port("chromium-win", chromium_win.ChromiumWinPort, - ports["chromium-win"]) - - def test_unknown_specified(self): - # Test what happens when you specify an unknown port. - orig_platform = sys.platform - self.assertRaises(NotImplementedError, factory.get, - port_name='unknown') - - def test_unknown_default(self): - # Test what happens when you're running on an unknown platform. - orig_platform = sys.platform - sys.platform = 'unknown' - self.assertRaises(NotImplementedError, factory.get) - sys.platform = orig_platform - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py deleted file mode 100644 index 8d94bb5..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome.py +++ /dev/null @@ -1,122 +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. - -# 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 - - -def _test_expectations_overrides(port, super): - # The chrome ports use the regular overrides plus anything in the - # official test_expectations as well. Hopefully we don't get collisions. - chromium_overrides = super.test_expectations_overrides(port) - - # FIXME: It used to be that AssertionError would get raised by - # path_from_chromium_base() if we weren't in a Chromium checkout, but - # this changed in r60427. This should probably be changed back. - overrides_path = port.path_from_chromium_base('webkit', 'tools', - 'layout_tests', 'test_expectations_chrome.txt') - if not os.path.exists(overrides_path): - return chromium_overrides - - with codecs.open(overrides_path, "r", "utf-8") as file: - if chromium_overrides: - return chromium_overrides + file.read() - else: - return file.read() - -def GetGoogleChromePort(**kwargs): - """Some tests have slightly different results when compiled as Google - Chrome vs Chromium. In those cases, we prepend an additional directory to - to the baseline paths.""" - port_name = kwargs['port_name'] - del kwargs['port_name'] - if port_name == 'google-chrome-linux32': - import chromium_linux - - class GoogleChromeLinux32Port(chromium_linux.ChromiumLinuxPort): - def baseline_search_path(self): - paths = chromium_linux.ChromiumLinuxPort.baseline_search_path( - self) - paths.insert(0, self._webkit_baseline_path( - 'google-chrome-linux32')) - return paths - - def test_expectations_overrides(self): - return _test_expectations_overrides(self, - chromium_linux.ChromiumLinuxPort) - - return GoogleChromeLinux32Port(**kwargs) - elif port_name == 'google-chrome-linux64': - import chromium_linux - - class GoogleChromeLinux64Port(chromium_linux.ChromiumLinuxPort): - def baseline_search_path(self): - paths = chromium_linux.ChromiumLinuxPort.baseline_search_path( - self) - paths.insert(0, self._webkit_baseline_path( - 'google-chrome-linux64')) - return paths - - def test_expectations_overrides(self): - return _test_expectations_overrides(self, - chromium_linux.ChromiumLinuxPort) - - return GoogleChromeLinux64Port(**kwargs) - elif port_name.startswith('google-chrome-mac'): - import chromium_mac - - class GoogleChromeMacPort(chromium_mac.ChromiumMacPort): - def baseline_search_path(self): - paths = chromium_mac.ChromiumMacPort.baseline_search_path( - self) - paths.insert(0, self._webkit_baseline_path( - 'google-chrome-mac')) - return paths - - def test_expectations_overrides(self): - return _test_expectations_overrides(self, - chromium_mac.ChromiumMacPort) - - return GoogleChromeMacPort(**kwargs) - elif port_name.startswith('google-chrome-win'): - import chromium_win - - class GoogleChromeWinPort(chromium_win.ChromiumWinPort): - def baseline_search_path(self): - paths = chromium_win.ChromiumWinPort.baseline_search_path( - self) - paths.insert(0, self._webkit_baseline_path( - 'google-chrome-win')) - return paths - - def test_expectations_overrides(self): - return _test_expectations_overrides(self, - chromium_win.ChromiumWinPort) - - return GoogleChromeWinPort(**kwargs) - raise NotImplementedError('unsupported port: %s' % port_name) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py deleted file mode 100644 index e60c274..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/google_chrome_unittest.py +++ /dev/null @@ -1,103 +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. - -# 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 codecs -import os -import unittest - -from webkitpy.common import newstringio - -import factory -import google_chrome - - -class GetGoogleChromePortTest(unittest.TestCase): - def test_get_google_chrome_port(self): - test_ports = ('google-chrome-linux32', 'google-chrome-linux64', - 'google-chrome-mac', 'google-chrome-win') - for port in test_ports: - self._verify_baseline_path(port, port) - self._verify_expectations_overrides(port) - - self._verify_baseline_path('google-chrome-mac', 'google-chrome-mac-leopard') - self._verify_baseline_path('google-chrome-win', 'google-chrome-win-xp') - self._verify_baseline_path('google-chrome-win', 'google-chrome-win-vista') - - def _verify_baseline_path(self, expected_path, port_name): - port = google_chrome.GetGoogleChromePort(port_name=port_name, - options=None) - path = port.baseline_search_path()[0] - self.assertEqual(expected_path, os.path.split(path)[1]) - - def _verify_expectations_overrides(self, port_name): - # FIXME: make this more robust when we have the Tree() abstraction. - # we should be able to test for the files existing or not, and - # be able to control the contents better. - - chromium_port = factory.get("chromium-mac") - chromium_overrides = chromium_port.test_expectations_overrides() - port = google_chrome.GetGoogleChromePort(port_name=port_name, - options=None) - - orig_exists = os.path.exists - orig_open = codecs.open - expected_string = "// hello, world\n" - - def mock_exists_chrome_not_found(path): - if 'test_expectations_chrome.txt' in path: - return False - return orig_exists(path) - - def mock_exists_chrome_found(path): - if 'test_expectations_chrome.txt' in path: - return True - return orig_exists(path) - - def mock_open(path, mode, encoding): - if 'test_expectations_chrome.txt' in path: - return newstringio.StringIO(expected_string) - return orig_open(path, mode, encoding) - - try: - os.path.exists = mock_exists_chrome_not_found - chrome_overrides = port.test_expectations_overrides() - self.assertEqual(chromium_overrides, chrome_overrides) - - os.path.exists = mock_exists_chrome_found - codecs.open = mock_open - chrome_overrides = port.test_expectations_overrides() - if chromium_overrides: - self.assertEqual(chrome_overrides, - chromium_overrides + expected_string) - else: - self.assertEqual(chrome_overrides, expected_string) - finally: - os.path.exists = orig_exists - codecs.open = orig_open - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/gtk.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/gtk.py deleted file mode 100644 index c60909e..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/gtk.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 Google name 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 Gtk implementation of the Port interface.""" - -import logging -import os - -from webkitpy.layout_tests.port.webkit import WebKitPort - -_log = logging.getLogger("webkitpy.layout_tests.port.gtk") - - -class GtkPort(WebKitPort): - """WebKit Gtk implementation of the Port class.""" - - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'gtk') - WebKitPort.__init__(self, **kwargs) - - def _tests_for_other_platforms(self): - # FIXME: This list could be dynamic based on platform name and - # pushed into base.Port. - # This really need to be automated. - return [ - "platform/chromium", - "platform/win", - "platform/qt", - "platform/mac", - ] - - def _path_to_apache_config_file(self): - # FIXME: This needs to detect the distribution and change config files. - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'apache2-debian-httpd.conf') - - def _shut_down_http_server(self, server_pid): - """Shut down the httpd web server. Blocks until it's fully - shut down. - - Args: - server_pid: The process ID of the running server. - """ - # server_pid is not set when "http_server.py stop" is run manually. - if server_pid is None: - # FIXME: This isn't ideal, since it could conflict with - # lighttpd processes not started by http_server.py, - # but good enough for now. - self._executive.kill_all('apache2') - else: - try: - os.kill(server_pid, signal.SIGTERM) - # TODO(mmoss) Maybe throw in a SIGKILL just to be sure? - except OSError: - # Sometimes we get a bad PID (e.g. from a stale httpd.pid - # file), so if kill fails on the given PID, just try to - # 'killall' web servers. - self._shut_down_http_server(None) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock.py deleted file mode 100644 index 8995b21..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged -# Copyright (C) 2010 Andras Becsi (abecsi@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 block NRWT threads when more NRWTs run -http and websocket tests in a same time.""" - -import glob -import logging -import os -import sys -import tempfile -import time - -from webkitpy.common.system.executive import Executive -from webkitpy.common.system.file_lock import FileLock - - -_log = logging.getLogger("webkitpy.layout_tests.port.http_lock") - - -class HttpLock(object): - - def __init__(self, lock_path, lock_file_prefix="WebKitHttpd.lock.", - guard_lock="WebKit.lock"): - self._lock_path = lock_path - if not self._lock_path: - self._lock_path = tempfile.gettempdir() - self._lock_file_prefix = lock_file_prefix - self._lock_file_path_prefix = os.path.join(self._lock_path, - self._lock_file_prefix) - self._guard_lock_file = os.path.join(self._lock_path, guard_lock) - self._guard_lock = FileLock(self._guard_lock_file) - self._process_lock_file_name = "" - self._executive = Executive() - - def cleanup_http_lock(self): - """Delete the lock file if exists.""" - if os.path.exists(self._process_lock_file_name): - _log.debug("Removing lock file: %s" % self._process_lock_file_name) - os.unlink(self._process_lock_file_name) - - def _extract_lock_number(self, lock_file_name): - """Return the lock number from lock file.""" - prefix_length = len(self._lock_file_path_prefix) - return int(lock_file_name[prefix_length:]) - - def _lock_file_list(self): - """Return the list of lock files sequentially.""" - lock_list = glob.glob(self._lock_file_path_prefix + '*') - lock_list.sort(key=self._extract_lock_number) - return lock_list - - def _next_lock_number(self): - """Return the next available lock number.""" - lock_list = self._lock_file_list() - if not lock_list: - return 0 - return self._extract_lock_number(lock_list[-1]) + 1 - - def _curent_lock_pid(self): - """Return with the current lock pid. If the lock is not valid - it deletes the lock file.""" - lock_list = self._lock_file_list() - if not lock_list: - return - try: - current_lock_file = open(lock_list[0], 'r') - current_pid = current_lock_file.readline() - current_lock_file.close() - if not (current_pid and self._executive.check_running_pid(int(current_pid))): - _log.debug("Removing stuck lock file: %s" % lock_list[0]) - os.unlink(lock_list[0]) - return - except (IOError, OSError): - return - return int(current_pid) - - def _create_lock_file(self): - """The lock files are used to schedule the running test sessions in first - come first served order. The guard lock ensures that the lock numbers are - sequential.""" - if not os.path.exists(self._lock_path): - _log.debug("Lock directory does not exist: %s" % self._lock_path) - return False - - if not self._guard_lock.acquire_lock(): - _log.debug("Guard lock timed out!") - return False - - self._process_lock_file_name = (self._lock_file_path_prefix + - str(self._next_lock_number())) - _log.debug("Creating lock file: %s" % self._process_lock_file_name) - lock_file = open(self._process_lock_file_name, 'w') - lock_file.write(str(os.getpid())) - lock_file.close() - self._guard_lock.release_lock() - return True - - - def wait_for_httpd_lock(self): - """Create a lock file and wait until it's turn comes. If something goes wrong - it wont do any locking.""" - if not self._create_lock_file(): - _log.debug("Warning, http locking failed!") - return - - while self._curent_lock_pid() != os.getpid(): - time.sleep(1) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock_unittest.py deleted file mode 100644 index 85c760a..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_lock_unittest.py +++ /dev/null @@ -1,111 +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 glob -import http_lock -import os -import unittest - - -class HttpLockTest(unittest.TestCase): - - def __init__(self, testFunc): - self.http_lock_obj = http_lock.HttpLock(None, "WebKitTestHttpd.lock.", "WebKitTest.lock") - self.lock_file_path_prefix = os.path.join(self.http_lock_obj._lock_path, - self.http_lock_obj._lock_file_prefix) - self.lock_file_name = self.lock_file_path_prefix + "0" - self.guard_lock_file = self.http_lock_obj._guard_lock_file - self.clean_all_lockfile() - unittest.TestCase.__init__(self, testFunc) - - def clean_all_lockfile(self): - if os.path.exists(self.guard_lock_file): - os.unlink(self.guard_lock_file) - lock_list = glob.glob(self.lock_file_path_prefix + '*') - for file_name in lock_list: - os.unlink(file_name) - - def assertEqual(self, first, second): - if first != second: - self.clean_all_lockfile() - unittest.TestCase.assertEqual(self, first, second) - - def _check_lock_file(self): - if os.path.exists(self.lock_file_name): - pid = os.getpid() - lock_file = open(self.lock_file_name, 'r') - lock_file_pid = lock_file.readline() - lock_file.close() - self.assertEqual(pid, int(lock_file_pid)) - return True - return False - - def test_lock_lifecycle(self): - self.http_lock_obj._create_lock_file() - - self.assertEqual(True, self._check_lock_file()) - self.assertEqual(1, self.http_lock_obj._next_lock_number()) - - self.http_lock_obj.cleanup_http_lock() - - self.assertEqual(False, self._check_lock_file()) - self.assertEqual(0, self.http_lock_obj._next_lock_number()) - - def test_extract_lock_number(self,): - lock_file_list = ( - self.lock_file_path_prefix + "00", - self.lock_file_path_prefix + "9", - self.lock_file_path_prefix + "001", - self.lock_file_path_prefix + "021", - ) - - expected_number_list = (0, 9, 1, 21) - - for lock_file, expected in zip(lock_file_list, expected_number_list): - self.assertEqual(self.http_lock_obj._extract_lock_number(lock_file), expected) - - def test_lock_file_list(self): - lock_file_list = [ - self.lock_file_path_prefix + "6", - self.lock_file_path_prefix + "1", - self.lock_file_path_prefix + "4", - self.lock_file_path_prefix + "3", - ] - - expected_file_list = [ - self.lock_file_path_prefix + "1", - self.lock_file_path_prefix + "3", - self.lock_file_path_prefix + "4", - self.lock_file_path_prefix + "6", - ] - - for file_name in lock_file_list: - open(file_name, 'w') - - self.assertEqual(self.http_lock_obj._lock_file_list(), expected_file_list) - - for file_name in lock_file_list: - os.unlink(file_name) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_server.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/http_server.py deleted file mode 100755 index 0f8a21e..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_server.py +++ /dev/null @@ -1,233 +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. - -"""A class to help start/stop the lighttpd server used by layout tests.""" - -from __future__ import with_statement - -import codecs -import logging -import optparse -import os -import shutil -import subprocess -import sys -import tempfile -import time -import urllib - -import factory -import http_server_base - -_log = logging.getLogger("webkitpy.layout_tests.port.http_server") - - -class HttpdNotStarted(Exception): - pass - - -class Lighttpd(http_server_base.HttpServerBase): - - def __init__(self, port_obj, output_dir, background=False, port=None, - root=None, run_background=None): - """Args: - output_dir: the absolute path to the layout test result directory - """ - # Webkit tests - http_server_base.HttpServerBase.__init__(self, port_obj) - self._output_dir = output_dir - self._process = None - self._port = port - self._root = root - self._run_background = run_background - if self._port: - self._port = int(self._port) - - try: - self._webkit_tests = os.path.join( - self._port_obj.layout_tests_dir(), 'http', 'tests') - self._js_test_resource = os.path.join( - self._port_obj.layout_tests_dir(), 'fast', 'js', 'resources') - except: - self._webkit_tests = None - self._js_test_resource = None - - # Self generated certificate for SSL server (for client cert get - # <base-path>\chrome\test\data\ssl\certs\root_ca_cert.crt) - self._pem_file = os.path.join( - os.path.dirname(os.path.abspath(__file__)), 'httpd2.pem') - - # One mapping where we can get to everything - self.VIRTUALCONFIG = [] - - if self._webkit_tests: - self.VIRTUALCONFIG.extend( - # Three mappings (one with SSL) for LayoutTests http tests - [{'port': 8000, 'docroot': self._webkit_tests}, - {'port': 8080, 'docroot': self._webkit_tests}, - {'port': 8443, 'docroot': self._webkit_tests, - 'sslcert': self._pem_file}]) - - def is_running(self): - return self._process != None - - def start(self): - if self.is_running(): - raise 'Lighttpd already running' - - base_conf_file = self._port_obj.path_from_webkit_base('WebKitTools', - 'Scripts', 'webkitpy', 'layout_tests', 'port', 'lighttpd.conf') - out_conf_file = os.path.join(self._output_dir, 'lighttpd.conf') - time_str = time.strftime("%d%b%Y-%H%M%S") - access_file_name = "access.log-" + time_str + ".txt" - access_log = os.path.join(self._output_dir, access_file_name) - log_file_name = "error.log-" + time_str + ".txt" - error_log = os.path.join(self._output_dir, log_file_name) - - # Remove old log files. We only need to keep the last ones. - self.remove_log_files(self._output_dir, "access.log-") - self.remove_log_files(self._output_dir, "error.log-") - - # Write out the config - with codecs.open(base_conf_file, "r", "utf-8") as file: - base_conf = file.read() - - # FIXME: This should be re-worked so that this block can - # use with open() instead of a manual file.close() call. - # lighttpd.conf files seem to be UTF-8 without BOM: - # http://redmine.lighttpd.net/issues/992 - f = codecs.open(out_conf_file, "w", "utf-8") - f.write(base_conf) - - # Write out our cgi handlers. Run perl through env so that it - # processes the #! line and runs perl with the proper command - # line arguments. Emulate apache's mod_asis with a cat cgi handler. - f.write(('cgi.assign = ( ".cgi" => "/usr/bin/env",\n' - ' ".pl" => "/usr/bin/env",\n' - ' ".asis" => "/bin/cat",\n' - ' ".php" => "%s" )\n\n') % - self._port_obj._path_to_lighttpd_php()) - - # Setup log files - f.write(('server.errorlog = "%s"\n' - 'accesslog.filename = "%s"\n\n') % (error_log, access_log)) - - # Setup upload folders. Upload folder is to hold temporary upload files - # and also POST data. This is used to support XHR layout tests that - # does POST. - f.write(('server.upload-dirs = ( "%s" )\n\n') % (self._output_dir)) - - # Setup a link to where the js test templates are stored - f.write(('alias.url = ( "/js-test-resources" => "%s" )\n\n') % - (self._js_test_resource)) - - # dump out of virtual host config at the bottom. - if self._root: - if self._port: - # Have both port and root dir. - mappings = [{'port': self._port, 'docroot': self._root}] - else: - # Have only a root dir - set the ports as for LayoutTests. - # This is used in ui_tests to run http tests against a browser. - - # default set of ports as for LayoutTests but with a - # specified root. - mappings = [{'port': 8000, 'docroot': self._root}, - {'port': 8080, 'docroot': self._root}, - {'port': 8443, 'docroot': self._root, - 'sslcert': self._pem_file}] - else: - mappings = self.VIRTUALCONFIG - for mapping in mappings: - ssl_setup = '' - if 'sslcert' in mapping: - ssl_setup = (' ssl.engine = "enable"\n' - ' ssl.pemfile = "%s"\n' % mapping['sslcert']) - - f.write(('$SERVER["socket"] == "127.0.0.1:%d" {\n' - ' server.document-root = "%s"\n' + - ssl_setup + - '}\n\n') % (mapping['port'], mapping['docroot'])) - f.close() - - executable = self._port_obj._path_to_lighttpd() - module_path = self._port_obj._path_to_lighttpd_modules() - start_cmd = [executable, - # Newly written config file - '-f', os.path.join(self._output_dir, 'lighttpd.conf'), - # Where it can find its module dynamic libraries - '-m', module_path] - - if not self._run_background: - start_cmd.append(# Don't background - '-D') - - # Copy liblightcomp.dylib to /tmp/lighttpd/lib to work around the - # bug that mod_alias.so loads it from the hard coded path. - if sys.platform == 'darwin': - tmp_module_path = '/tmp/lighttpd/lib' - if not os.path.exists(tmp_module_path): - os.makedirs(tmp_module_path) - lib_file = 'liblightcomp.dylib' - shutil.copyfile(os.path.join(module_path, lib_file), - os.path.join(tmp_module_path, lib_file)) - - env = self._port_obj.setup_environ_for_server() - _log.debug('Starting http server') - # FIXME: Should use Executive.run_command - self._process = subprocess.Popen(start_cmd, env=env) - - # Wait for server to start. - self.mappings = mappings - server_started = self.wait_for_action( - self.is_server_running_on_all_ports) - - # Our process terminated already - if not server_started or self._process.returncode != None: - raise google.httpd_utils.HttpdNotStarted('Failed to start httpd.') - - _log.debug("Server successfully started") - - # TODO(deanm): Find a nicer way to shutdown cleanly. Our log files are - # probably not being flushed, etc... why doesn't our python have os.kill ? - - def stop(self, force=False): - if not force and not self.is_running(): - return - - httpd_pid = None - if self._process: - httpd_pid = self._process.pid - self._port_obj._shut_down_http_server(httpd_pid) - - if self._process: - # wait() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - self._process.wait() - self._process = None diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_server_base.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/http_server_base.py deleted file mode 100644 index 2745cce..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/http_server_base.py +++ /dev/null @@ -1,81 +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. - -"""Base class with common routines between the Apache and Lighttpd servers.""" - -import logging -import os -import time -import urllib - -_log = logging.getLogger("webkitpy.layout_tests.port.http_server_base") - - -class HttpServerBase(object): - - def __init__(self, port_obj): - self._port_obj = port_obj - - def wait_for_action(self, action): - """Repeat the action for 20 seconds or until it succeeds. Returns - whether it succeeded.""" - start_time = time.time() - while time.time() - start_time < 20: - if action(): - return True - _log.debug("Waiting for action: %s" % action) - time.sleep(1) - - return False - - def is_server_running_on_all_ports(self): - """Returns whether the server is running on all the desired ports.""" - for mapping in self.mappings: - if 'sslcert' in mapping: - http_suffix = 's' - else: - http_suffix = '' - - url = 'http%s://127.0.0.1:%d/' % (http_suffix, mapping['port']) - - try: - response = urllib.urlopen(url) - _log.debug("Server running at %s" % url) - except IOError, e: - _log.debug("Server NOT running at %s: %s" % (url, e)) - return False - - return True - - def remove_log_files(self, folder, starts_with): - files = os.listdir(folder) - for file in files: - if file.startswith(starts_with): - full_path = os.path.join(folder, file) - os.remove(full_path) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/httpd2.pem b/WebKitTools/Scripts/webkitpy/layout_tests/port/httpd2.pem deleted file mode 100644 index 6349b78..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/httpd2.pem +++ /dev/null @@ -1,41 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEZDCCAkygAwIBAgIBATANBgkqhkiG9w0BAQUFADBgMRAwDgYDVQQDEwdUZXN0 -IENBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN -TW91bnRhaW4gVmlldzESMBAGA1UEChMJQ2VydCBUZXN0MB4XDTA4MDcyODIyMzIy -OFoXDTEzMDcyNzIyMzIyOFowSjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlm -b3JuaWExEjAQBgNVBAoTCUNlcnQgVGVzdDESMBAGA1UEAxMJMTI3LjAuMC4xMIGf -MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQj2tPWPUgbuI4H3/3dnttqVbndwU3 -3BdRCd67DFM44GRrsjDSH4bY/EbFyX9D52d/iy6ZaAmDePcCz5k/fgP3DMujykYG -qgNiV2ywxTlMj7NlN2C7SRt68fQMZr5iI7rypdxuaZt9lSMD3ENBffYtuLTyZd9a -3JPJe1TaIab5GwIDAQABo4HCMIG/MAkGA1UdEwQCMAAwHQYDVR0OBBYEFCYLBv5K -x5sLNVlpLh5FwTwhdDl7MIGSBgNVHSMEgYowgYeAFF3Of5nj1BlBMU/Gz7El9Vqv -45cxoWSkYjBgMRAwDgYDVQQDEwdUZXN0IENBMQswCQYDVQQGEwJVUzETMBEGA1UE -CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzESMBAGA1UEChMJ -Q2VydCBUZXN0ggkA1FGT1D/e2U4wDQYJKoZIhvcNAQEFBQADggIBAEtkVmLObUgk -b2cIA2S+QDtifq1UgVfBbytvR2lFmnADOR55mo0gHQG3HHqq4g034LmoVXDHhUk8 -Gb6aFiv4QubmVhLXcUelTRXwiNvGzkW7pC6Jrq105hdPjzXMKTcmiLaopm5Fqfc7 -hj5Cn1Sjspc8pdeQjrbeMdvca7KlFrGP8YkwCU2xOOX9PiN9G0966BWfjnr/fZZp -+OQVuUFHdiAZwthEMuDpAAXHqYXIsermgdOpgJaA53cf8NqBV2QGhtFgtsJCRoiu -7DKqhyRWBGyz19VIH2b7y+6qvQVxuHk19kKRM0nftw/yNcJnm7gtttespMUPsOMa -a2SD1G0hm0TND6vxaBhgR3cVqpl/qIpAdFi00Tm7hTyYE7I43zPW03t+/DpCt3Um -EMRZsQ90co5q+bcx/vQ7YAtwUh30uMb0wpibeyCwDp8cqNmSiRkEuc/FjTYes5t8 -5gR//WX1l0+qjrjusO9NmoLnq2Yk6UcioX+z+q6Z/dudGfqhLfeWD2Q0LWYA242C -d7km5Y3KAt1PJdVsof/aiVhVdddY/OIEKTRQhWEdDbosy2eh16BCKXT2FFvhNDg1 -AYFvn6I8nj9IldMJiIc3DdhacEAEzRMeRgPdzAa1griKUGknxsyTyRii8ru0WS6w -DCNrlDOVXdzYGEZooBI76BDVY0W0akjV ------END CERTIFICATE----- ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQDQj2tPWPUgbuI4H3/3dnttqVbndwU33BdRCd67DFM44GRrsjDS -H4bY/EbFyX9D52d/iy6ZaAmDePcCz5k/fgP3DMujykYGqgNiV2ywxTlMj7NlN2C7 -SRt68fQMZr5iI7rypdxuaZt9lSMD3ENBffYtuLTyZd9a3JPJe1TaIab5GwIDAQAB -AoGANHXu8z2YIzlhE+bwhGm8MGBpKL3qhRuKjeriqMA36tWezOw8lY4ymEAU+Ulv -BsCdaxqydQoTYou57m4TyUHEcxq9pq3H0zB0qL709DdHi/t4zbV9XIoAzC5v0/hG -9+Ca29TwC02FCw+qLkNrtwCpwOcQmc+bPxqvFu1iMiahURECQQD2I/Hi2413CMZz -TBjl8fMiVO9GhA2J0sc8Qi+YcgJakaLD9xcbaiLkTzPZDlA389C1b6Ia+poAr4YA -Ve0FFbxpAkEA2OobayyHE/QtPEqoy6NLR57jirmVBNmSWWd4lAyL5UIHIYVttJZg -8CLvbzaU/iDGwR+wKsM664rKPHEmtlyo4wJBAMeSqYO5ZOCJGu9NWjrHjM3fdAsG -8zs2zhiLya+fcU0iHIksBW5TBmt71Jw/wMc9R5J1K0kYvFml98653O5si1ECQBCk -RV4/mE1rmlzZzYFyEcB47DQkcM5ictvxGEsje0gnfKyRtAz6zI0f4QbDRUMJ+LWw -XK+rMsYHa+SfOb0b9skCQQCLdeonsIpFDv/Uv+flHISy0WA+AFkLXrRkBKh6G/OD -dMHaNevkJgUnpceVEnkrdenp5CcEoFTI17pd+nBgDm/B ------END RSA PRIVATE KEY----- diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/lighttpd.conf b/WebKitTools/Scripts/webkitpy/layout_tests/port/lighttpd.conf deleted file mode 100644 index 26ca22f..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/lighttpd.conf +++ /dev/null @@ -1,90 +0,0 @@ -server.tag = "LightTPD/1.4.19 (Win32)" -server.modules = ( "mod_accesslog", - "mod_alias", - "mod_cgi", - "mod_rewrite" ) - -# default document root required -server.document-root = "." - -# files to check for if .../ is requested -index-file.names = ( "index.php", "index.pl", "index.cgi", - "index.html", "index.htm", "default.htm" ) -# mimetype mapping -mimetype.assign = ( - ".gif" => "image/gif", - ".jpg" => "image/jpeg", - ".jpeg" => "image/jpeg", - ".png" => "image/png", - ".svg" => "image/svg+xml", - ".css" => "text/css", - ".html" => "text/html", - ".htm" => "text/html", - ".xhtml" => "application/xhtml+xml", - ".xhtmlmp" => "application/vnd.wap.xhtml+xml", - ".js" => "application/x-javascript", - ".log" => "text/plain", - ".conf" => "text/plain", - ".text" => "text/plain", - ".txt" => "text/plain", - ".dtd" => "text/xml", - ".xml" => "text/xml", - ".manifest" => "text/cache-manifest", - ) - -# Use the "Content-Type" extended attribute to obtain mime type if possible -mimetype.use-xattr = "enable" - -## -# which extensions should not be handle via static-file transfer -# -# .php, .pl, .fcgi are most often handled by mod_fastcgi or mod_cgi -static-file.exclude-extensions = ( ".php", ".pl", ".cgi" ) - -server.bind = "localhost" -server.port = 8001 - -## virtual directory listings -dir-listing.activate = "enable" -#dir-listing.encoding = "iso-8859-2" -#dir-listing.external-css = "style/oldstyle.css" - -## enable debugging -#debug.log-request-header = "enable" -#debug.log-response-header = "enable" -#debug.log-request-handling = "enable" -#debug.log-file-not-found = "enable" - -#### SSL engine -#ssl.engine = "enable" -#ssl.pemfile = "server.pem" - -# Rewrite rule for utf-8 path test (LayoutTests/http/tests/uri/utf8-path.html) -# See the apache rewrite rule at LayoutTests/http/tests/uri/intercept/.htaccess -# Rewrite rule for LayoutTests/http/tests/appcache/cyrillic-uri.html. -# See the apache rewrite rule at -# LayoutTests/http/tests/appcache/resources/intercept/.htaccess -url.rewrite-once = ( - "^/uri/intercept/(.*)" => "/uri/resources/print-uri.php", - "^/appcache/resources/intercept/(.*)" => "/appcache/resources/print-uri.php" -) - -# LayoutTests/http/tests/xmlhttprequest/response-encoding.html uses an htaccess -# to override charset for reply2.txt, reply2.xml, and reply4.txt. -$HTTP["url"] =~ "^/xmlhttprequest/resources/reply2.(txt|xml)" { - mimetype.assign = ( - ".txt" => "text/plain; charset=windows-1251", - ".xml" => "text/xml; charset=windows-1251" - ) -} -$HTTP["url"] =~ "^/xmlhttprequest/resources/reply4.txt" { - mimetype.assign = ( ".txt" => "text/plain; charset=koi8-r" ) -} - -# LayoutTests/http/tests/appcache/wrong-content-type.html uses an htaccess -# to override mime type for wrong-content-type.manifest. -$HTTP["url"] =~ "^/appcache/resources/wrong-content-type.manifest" { - mimetype.assign = ( ".manifest" => "text/plain" ) -} - -# Autogenerated test-specific config follows. diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py deleted file mode 100644 index 696e339..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/mac.py +++ /dev/null @@ -1,152 +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 Google name 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 Mac implementation of the Port interface.""" - -import logging -import os -import platform -import signal - -import webkitpy.common.system.ospath as ospath -import webkitpy.layout_tests.port.server_process as server_process -from webkitpy.layout_tests.port.webkit import WebKitPort, WebKitDriver - -_log = logging.getLogger("webkitpy.layout_tests.port.mac") - - -class MacPort(WebKitPort): - """WebKit Mac implementation of the Port class.""" - - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'mac' + self.version()) - WebKitPort.__init__(self, **kwargs) - - def default_child_processes(self): - # FIXME: new-run-webkit-tests is unstable on Mac running more than - # four threads in parallel. - # See https://bugs.webkit.org/show_bug.cgi?id=36622 - child_processes = WebKitPort.default_child_processes(self) - if child_processes > 4: - return 4 - return child_processes - - def baseline_search_path(self): - port_names = [] - if self._name == 'mac-tiger': - port_names.append("mac-tiger") - if self._name in ('mac-tiger', 'mac-leopard'): - port_names.append("mac-leopard") - if self._name in ('mac-tiger', 'mac-leopard', 'mac-snowleopard'): - port_names.append("mac-snowleopard") - port_names.append("mac") - return map(self._webkit_baseline_path, port_names) - - def path_to_test_expectations_file(self): - return self.path_from_webkit_base('LayoutTests', 'platform', - 'mac', 'test_expectations.txt') - - def _skipped_file_paths(self): - # FIXME: This method will need to be made work for non-mac - # platforms and moved into base.Port. - skipped_files = [] - if self._name in ('mac-tiger', 'mac-leopard', 'mac-snowleopard'): - skipped_files.append(os.path.join( - self._webkit_baseline_path(self._name), 'Skipped')) - skipped_files.append(os.path.join(self._webkit_baseline_path('mac'), - 'Skipped')) - return skipped_files - - def test_platform_name(self): - return 'mac' + self.version() - - def version(self): - os_version_string = platform.mac_ver()[0] # e.g. "10.5.6" - if not os_version_string: - return '-leopard' - release_version = int(os_version_string.split('.')[1]) - if release_version == 4: - return '-tiger' - elif release_version == 5: - return '-leopard' - elif release_version == 6: - return '-snowleopard' - return '' - - def _build_java_test_support(self): - java_tests_path = os.path.join(self.layout_tests_dir(), "java") - build_java = ["/usr/bin/make", "-C", java_tests_path] - if self._executive.run_command(build_java, return_exit_code=True): - _log.error("Failed to build Java support files: %s" % build_java) - return False - return True - - def _check_port_build(self): - return self._build_java_test_support() - - def _tests_for_other_platforms(self): - # The original run-webkit-tests builds up a "whitelist" of tests to - # run, and passes that to DumpRenderTree. new-run-webkit-tests assumes - # we run *all* tests and test_expectations.txt functions as a - # blacklist. - # FIXME: This list could be dynamic based on platform name and - # pushed into base.Port. - return [ - "platform/chromium", - "platform/gtk", - "platform/qt", - "platform/win", - ] - - def _path_to_apache_config_file(self): - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'apache2-httpd.conf') - - # FIXME: This doesn't have anything to do with WebKit. - def _shut_down_http_server(self, server_pid): - """Shut down the lighttpd web server. Blocks until it's fully - shut down. - - Args: - server_pid: The process ID of the running server. - """ - # server_pid is not set when "http_server.py stop" is run manually. - if server_pid is None: - # FIXME: This isn't ideal, since it could conflict with - # lighttpd processes not started by http_server.py, - # but good enough for now. - self._executive.kill_all('httpd') - else: - try: - os.kill(server_pid, signal.SIGTERM) - # FIXME: Maybe throw in a SIGKILL just to be sure? - except OSError: - # Sometimes we get a bad PID (e.g. from a stale httpd.pid - # file), so if kill fails on the given PID, just try to - # 'killall' web servers. - self._shut_down_http_server(None) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/mac_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/mac_unittest.py deleted file mode 100644 index d383a4c..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/mac_unittest.py +++ /dev/null @@ -1,81 +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 StringIO -import sys -import unittest - -import mac -import port_testcase - - -class MacTest(port_testcase.PortTestCase): - def make_port(self, options=port_testcase.mock_options): - if sys.platform != 'darwin': - return None - port_obj = mac.MacPort(options=options) - port_obj._options.results_directory = port_obj.results_directory() - port_obj._options.configuration = 'Release' - return port_obj - - def test_skipped_file_paths(self): - port = self.make_port() - if not port: - return - skipped_paths = port._skipped_file_paths() - # FIXME: _skipped_file_paths should return WebKit-relative paths. - # So to make it unit testable, we strip the WebKit directory from the path. - relative_paths = [path[len(port.path_from_webkit_base()):] for path in skipped_paths] - self.assertEqual(relative_paths, ['LayoutTests/platform/mac-leopard/Skipped', 'LayoutTests/platform/mac/Skipped']) - - example_skipped_file = u""" -# <rdar://problem/5647952> fast/events/mouseout-on-window.html needs mac DRT to issue mouse out events -fast/events/mouseout-on-window.html - -# <rdar://problem/5643675> window.scrollTo scrolls a window with no scrollbars -fast/events/attempt-scroll-with-no-scrollbars.html - -# see bug <rdar://problem/5646437> REGRESSION (r28015): svg/batik/text/smallFonts fails -svg/batik/text/smallFonts.svg -""" - example_skipped_tests = [ - "fast/events/mouseout-on-window.html", - "fast/events/attempt-scroll-with-no-scrollbars.html", - "svg/batik/text/smallFonts.svg", - ] - - def test_skipped_file_paths(self): - port = self.make_port() - if not port: - return - skipped_file = StringIO.StringIO(self.example_skipped_file) - self.assertEqual(port._tests_from_skipped_file(skipped_file), self.example_skipped_tests) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/port_testcase.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/port_testcase.py deleted file mode 100644 index c4b36ac..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/port_testcase.py +++ /dev/null @@ -1,97 +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. - -"""Unit testing base class for Port implementations.""" - -import os -import tempfile -import unittest - -from webkitpy.tool import mocktool -mock_options = mocktool.MockOptions(results_directory='layout-test-results', - use_apache=True, - configuration='Release') - -# FIXME: This should be used for all ports, not just WebKit Mac. See -# https://bugs.webkit.org/show_bug.cgi?id=50043 . - -class PortTestCase(unittest.TestCase): - """Tests the WebKit port implementation.""" - def make_port(self, options=mock_options): - """Override in subclass.""" - raise NotImplementedError() - - def test_driver_cmd_line(self): - port = self.make_port() - if not port: - return - self.assertTrue(len(port.driver_cmd_line())) - - def test_http_server(self): - port = self.make_port() - if not port: - return - port.start_http_server() - port.stop_http_server() - - def test_image_diff(self): - port = self.make_port() - if not port: - return - - # FIXME: not sure why this shouldn't always be True - #self.assertTrue(port.check_image_diff()) - if not port.check_image_diff(): - return - - dir = port.layout_tests_dir() - file1 = os.path.join(dir, 'fast', 'css', 'button_center.png') - fh1 = file(file1) - contents1 = fh1.read() - file2 = os.path.join(dir, 'fast', 'css', - 'remove-shorthand-expected.png') - fh2 = file(file2) - contents2 = fh2.read() - tmpfile = tempfile.mktemp() - - self.assertFalse(port.diff_image(contents1, contents1)) - self.assertTrue(port.diff_image(contents1, contents2)) - - self.assertTrue(port.diff_image(contents1, contents2, tmpfile)) - fh1.close() - fh2.close() - # FIXME: this may not be being written? - # self.assertTrue(os.path.exists(tmpfile)) - # os.remove(tmpfile) - - def test_websocket_server(self): - port = self.make_port() - if not port: - return - port.start_websocket_server() - port.stop_websocket_server() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/qt.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/qt.py deleted file mode 100644 index af94acc..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/qt.py +++ /dev/null @@ -1,119 +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 Google name 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. - -"""QtWebKit implementation of the Port interface.""" - -import logging -import os -import signal -import sys - -import webkit - -from webkitpy.layout_tests.port.webkit import WebKitPort - -_log = logging.getLogger("webkitpy.layout_tests.port.qt") - - -class QtPort(WebKitPort): - """QtWebKit implementation of the Port class.""" - - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'qt') - WebKitPort.__init__(self, **kwargs) - - def baseline_search_path(self): - port_names = [] - if sys.platform == 'linux2': - port_names.append("qt-linux") - elif sys.platform in ('win32', 'cygwin'): - port_names.append("qt-win") - elif sys.platform == 'darwin': - port_names.append("qt-mac") - port_names.append("qt") - return map(self._webkit_baseline_path, port_names) - - def _tests_for_other_platforms(self): - # FIXME: This list could be dynamic based on platform name and - # pushed into base.Port. - # This really need to be automated. - return [ - "platform/chromium", - "platform/win", - "platform/gtk", - "platform/mac", - ] - - def _path_to_apache_config_file(self): - # FIXME: This needs to detect the distribution and change config files. - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'apache2-debian-httpd.conf') - - def _shut_down_http_server(self, server_pid): - """Shut down the httpd web server. Blocks until it's fully - shut down. - - Args: - server_pid: The process ID of the running server. - """ - # server_pid is not set when "http_server.py stop" is run manually. - if server_pid is None: - # FIXME: This isn't ideal, since it could conflict with - # lighttpd processes not started by http_server.py, - # but good enough for now. - self._executive.kill_all('apache2') - else: - try: - os.kill(server_pid, signal.SIGTERM) - # TODO(mmoss) Maybe throw in a SIGKILL just to be sure? - except OSError: - # Sometimes we get a bad PID (e.g. from a stale httpd.pid - # file), so if kill fails on the given PID, just try to - # 'killall' web servers. - self._shut_down_http_server(None) - - def _build_driver(self): - # The Qt port builds DRT as part of the main build step - return True - - def _path_to_driver(self): - return self._build_path('bin/DumpRenderTree') - - def _path_to_image_diff(self): - return self._build_path('bin/ImageDiff') - - def _path_to_webcore_library(self): - return self._build_path('lib/libQtWebKit.so') - - def _runtime_feature_list(self): - return None - - def setup_environ_for_server(self): - env = webkit.WebKitPort.setup_environ_for_server(self) - env['QTWEBKIT_PLUGIN_PATH'] = self._build_path('lib/plugins') - return env diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py deleted file mode 100644 index 5a0a40c..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/server_process.py +++ /dev/null @@ -1,225 +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 Google name 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 implements the ServerProcess wrapper class""" - -import logging -import os -import select -import signal -import subprocess -import sys -import time -if sys.platform != 'win32': - import fcntl - -from webkitpy.common.system.executive import Executive - -_log = logging.getLogger("webkitpy.layout_tests.port.server_process") - - -class ServerProcess: - """This class provides a wrapper around a subprocess that - implements a simple request/response usage model. The primary benefit - is that reading responses takes a timeout, so that we don't ever block - indefinitely. The class also handles transparently restarting processes - as necessary to keep issuing commands.""" - - def __init__(self, port_obj, name, cmd, env=None, executive=Executive()): - self._port = port_obj - self._name = name - self._cmd = cmd - self._env = env - self._reset() - self._executive = executive - - def _reset(self): - self._proc = None - self._output = '' - self.crashed = False - self.timed_out = False - self.error = '' - - def _start(self): - if self._proc: - raise ValueError("%s already running" % self._name) - self._reset() - # close_fds is a workaround for http://bugs.python.org/issue2320 - close_fds = sys.platform not in ('win32', 'cygwin') - self._proc = subprocess.Popen(self._cmd, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=close_fds, - env=self._env) - fd = self._proc.stdout.fileno() - fl = fcntl.fcntl(fd, fcntl.F_GETFL) - fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) - fd = self._proc.stderr.fileno() - fl = fcntl.fcntl(fd, fcntl.F_GETFL) - fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) - - def handle_interrupt(self): - """This routine checks to see if the process crashed or exited - because of a keyboard interrupt and raises KeyboardInterrupt - accordingly.""" - if self.crashed: - # This is hex code 0xc000001d, which is used for abrupt - # termination. This happens if we hit ctrl+c from the prompt - # and we happen to be waiting on the DumpRenderTree. - # sdoyon: Not sure for which OS and in what circumstances the - # above code is valid. What works for me under Linux to detect - # ctrl+c is for the subprocess returncode to be negative - # SIGINT. And that agrees with the subprocess documentation. - if (-1073741510 == self._proc.returncode or - - signal.SIGINT == self._proc.returncode): - raise KeyboardInterrupt - return - - def poll(self): - """Check to see if the underlying process is running; returns None - if it still is (wrapper around subprocess.poll).""" - if self._proc: - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - return self._proc.poll() - return None - - def write(self, input): - """Write a request to the subprocess. The subprocess is (re-)start()'ed - if is not already running.""" - if not self._proc: - self._start() - self._proc.stdin.write(input) - - def read_line(self, timeout): - """Read a single line from the subprocess, waiting until the deadline. - If the deadline passes, the call times out. Note that even if the - subprocess has crashed or the deadline has passed, if there is output - pending, it will be returned. - - Args: - timeout: floating-point number of seconds the call is allowed - to block for. A zero or negative number will attempt to read - any existing data, but will not block. There is no way to - block indefinitely. - Returns: - output: data returned, if any. If no data is available and the - call times out or crashes, an empty string is returned. Note - that the returned string includes the newline ('\n').""" - return self._read(timeout, size=0) - - def read(self, timeout, size): - """Attempts to read size characters from the subprocess, waiting until - the deadline passes. If the deadline passes, any available data will be - returned. Note that even if the deadline has passed or if the - subprocess has crashed, any available data will still be returned. - - Args: - timeout: floating-point number of seconds the call is allowed - to block for. A zero or negative number will attempt to read - any existing data, but will not block. There is no way to - block indefinitely. - size: amount of data to read. Must be a postive integer. - Returns: - output: data returned, if any. If no data is available, an empty - string is returned. - """ - if size <= 0: - raise ValueError('ServerProcess.read() called with a ' - 'non-positive size: %d ' % size) - return self._read(timeout, size) - - def _read(self, timeout, size): - """Internal routine that actually does the read.""" - index = -1 - out_fd = self._proc.stdout.fileno() - err_fd = self._proc.stderr.fileno() - select_fds = (out_fd, err_fd) - deadline = time.time() + timeout - while not self.timed_out and not self.crashed: - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - if self._proc.poll() != None: - self.crashed = True - self.handle_interrupt() - - now = time.time() - if now > deadline: - self.timed_out = True - - # Check to see if we have any output we can return. - if size and len(self._output) >= size: - index = size - elif size == 0: - index = self._output.find('\n') + 1 - - if index > 0 or self.crashed or self.timed_out: - output = self._output[0:index] - self._output = self._output[index:] - return output - - # Nope - wait for more data. - (read_fds, write_fds, err_fds) = select.select(select_fds, [], - select_fds, - deadline - now) - try: - if out_fd in read_fds: - self._output += self._proc.stdout.read() - if err_fd in read_fds: - self.error += self._proc.stderr.read() - except IOError, e: - pass - - def stop(self): - """Stop (shut down) the subprocess), if it is running.""" - pid = self._proc.pid - self._proc.stdin.close() - self._proc.stdout.close() - if self._proc.stderr: - self._proc.stderr.close() - if sys.platform not in ('win32', 'cygwin'): - # Closing stdin/stdout/stderr hangs sometimes on OS X, - # (see restart(), above), and anyway we don't want to hang - # the harness if DumpRenderTree is buggy, so we wait a couple - # seconds to give DumpRenderTree a chance to clean up, but then - # force-kill the process if necessary. - KILL_TIMEOUT = 3.0 - timeout = time.time() + KILL_TIMEOUT - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - while self._proc.poll() is None and time.time() < timeout: - time.sleep(0.1) - # poll() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - if self._proc.poll() is None: - _log.warning('stopping %s timed out, killing it' % - self._name) - self._executive.kill_process(self._proc.pid) - _log.warning('killed') - self._reset() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py deleted file mode 100644 index 8e27f35..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/test.py +++ /dev/null @@ -1,312 +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 Google name 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. - -"""Dummy Port implementation used for testing.""" -from __future__ import with_statement - -import codecs -import fnmatch -import os -import sys -import time - -from webkitpy.layout_tests.layout_package import test_output - -import base - - -# This sets basic expectations for a test. Each individual expectation -# can be overridden by a keyword argument in TestList.add(). -class TestInstance: - def __init__(self, name): - self.name = name - self.base = name[(name.rfind("/") + 1):name.rfind(".html")] - self.crash = False - self.exception = False - self.hang = False - self.keyboard = False - self.error = '' - self.timeout = False - self.actual_text = self.base + '-txt\n' - self.actual_checksum = self.base + '-checksum\n' - self.actual_image = self.base + '-png\n' - self.expected_text = self.actual_text - self.expected_checksum = self.actual_checksum - self.expected_image = self.actual_image - - -# This is an in-memory list of tests, what we want them to produce, and -# what we want to claim are the expected results. -class TestList: - def __init__(self, port): - self.port = port - self.tests = {} - - def add(self, name, **kwargs): - test = TestInstance(name) - for key, value in kwargs.items(): - test.__dict__[key] = value - self.tests[name] = test - - def keys(self): - return self.tests.keys() - - def __contains__(self, item): - return item in self.tests - - def __getitem__(self, item): - return self.tests[item] - - -class TestPort(base.Port): - """Test implementation of the Port interface.""" - - def __init__(self, **kwargs): - base.Port.__init__(self, **kwargs) - tests = TestList(self) - tests.add('passes/image.html') - tests.add('passes/text.html') - tests.add('failures/expected/checksum.html', - actual_checksum='checksum_fail-checksum') - tests.add('failures/expected/crash.html', crash=True) - tests.add('failures/expected/exception.html', exception=True) - tests.add('failures/expected/timeout.html', timeout=True) - tests.add('failures/expected/hang.html', hang=True) - tests.add('failures/expected/missing_text.html', - expected_text=None) - tests.add('failures/expected/image.html', - actual_image='image_fail-png', - expected_image='image-png') - tests.add('failures/expected/image_checksum.html', - actual_checksum='image_checksum_fail-checksum', - actual_image='image_checksum_fail-png') - tests.add('failures/expected/keyboard.html', - keyboard=True) - tests.add('failures/expected/missing_check.html', - expected_checksum=None) - tests.add('failures/expected/missing_image.html', - expected_image=None) - tests.add('failures/expected/missing_text.html', - expected_text=None) - tests.add('failures/expected/text.html', - actual_text='text_fail-png') - tests.add('failures/unexpected/text-image-checksum.html', - actual_text='text-image-checksum_fail-txt', - actual_checksum='text-image-checksum_fail-checksum') - tests.add('http/tests/passes/text.html') - tests.add('http/tests/ssl/text.html') - tests.add('passes/error.html', error='stuff going to stderr') - tests.add('passes/image.html') - tests.add('passes/platform_image.html') - tests.add('passes/text.html') - tests.add('websocket/tests/passes/text.html') - self._tests = tests - - def baseline_path(self): - return os.path.join(self.layout_tests_dir(), 'platform', - self.name() + self.version()) - - def baseline_search_path(self): - return [self.baseline_path()] - - def check_build(self, needs_http): - return True - - def diff_image(self, expected_contents, actual_contents, - diff_filename=None): - diffed = actual_contents != expected_contents - if diffed and diff_filename: - with codecs.open(diff_filename, "w", "utf-8") as diff_fh: - diff_fh.write("< %s\n---\n> %s\n" % - (expected_contents, actual_contents)) - return diffed - - def expected_checksum(self, test): - test = self.relative_test_filename(test) - return self._tests[test].expected_checksum - - def expected_image(self, test): - test = self.relative_test_filename(test) - return self._tests[test].expected_image - - def expected_text(self, test): - test = self.relative_test_filename(test) - text = self._tests[test].expected_text - if not text: - text = '' - return text - - def tests(self, paths): - # Test the idea of port-specific overrides for test lists. Also - # keep in memory to speed up the test harness. - if not paths: - paths = ['*'] - - matched_tests = [] - for p in paths: - if self.path_isdir(p): - matched_tests.extend(fnmatch.filter(self._tests.keys(), p + '*')) - else: - matched_tests.extend(fnmatch.filter(self._tests.keys(), p)) - layout_tests_dir = self.layout_tests_dir() - return set([os.path.join(layout_tests_dir, p) for p in matched_tests]) - - def path_exists(self, path): - # used by test_expectations.py and printing.py - rpath = self.relative_test_filename(path) - if rpath in self._tests: - return True - if self.path_isdir(rpath): - return True - if rpath.endswith('-expected.txt'): - test = rpath.replace('-expected.txt', '.html') - return (test in self._tests and - self._tests[test].expected_text) - if rpath.endswith('-expected.checksum'): - test = rpath.replace('-expected.checksum', '.html') - return (test in self._tests and - self._tests[test].expected_checksum) - if rpath.endswith('-expected.png'): - test = rpath.replace('-expected.png', '.html') - return (test in self._tests and - self._tests[test].expected_image) - return False - - def layout_tests_dir(self): - return self.path_from_webkit_base('WebKitTools', 'Scripts', - 'webkitpy', 'layout_tests', 'data') - - def path_isdir(self, path): - # Used by test_expectations.py - # - # We assume that a path is a directory if we have any tests that - # whose prefix matches the path plus a directory modifier. - if path[-1] != '/': - path += '/' - return any([t.startswith(path) for t in self._tests.keys()]) - - def test_dirs(self): - return ['passes', 'failures'] - - def name(self): - return self._name - - def _path_to_wdiff(self): - return None - - def results_directory(self): - return '/tmp/' + self.get_option('results_directory') - - def setup_test_run(self): - pass - - def create_driver(self, worker_number): - return TestDriver(self, worker_number) - - def start_http_server(self): - pass - - def start_websocket_server(self): - pass - - def stop_http_server(self): - pass - - def stop_websocket_server(self): - pass - - def test_expectations(self): - """Returns the test expectations for this port. - - Basically this string should contain the equivalent of a - test_expectations file. See test_expectations.py for more details.""" - return """ -WONTFIX : failures/expected/checksum.html = IMAGE -WONTFIX : failures/expected/crash.html = CRASH -// This one actually passes because the checksums will match. -WONTFIX : failures/expected/image.html = PASS -WONTFIX : failures/expected/image_checksum.html = IMAGE -WONTFIX : failures/expected/missing_check.html = MISSING PASS -WONTFIX : failures/expected/missing_image.html = MISSING PASS -WONTFIX : failures/expected/missing_text.html = MISSING PASS -WONTFIX : failures/expected/text.html = TEXT -WONTFIX : failures/expected/timeout.html = TIMEOUT -WONTFIX SKIP : failures/expected/hang.html = TIMEOUT -WONTFIX SKIP : failures/expected/keyboard.html = CRASH -WONTFIX SKIP : failures/expected/exception.html = CRASH -""" - - def test_base_platform_names(self): - return ('mac', 'win') - - def test_platform_name(self): - return 'mac' - - def test_platform_names(self): - return self.test_base_platform_names() - - def test_platform_name_to_name(self, test_platform_name): - return test_platform_name - - def version(self): - return '' - - -class TestDriver(base.Driver): - """Test/Dummy implementation of the DumpRenderTree interface.""" - - def __init__(self, port, worker_number): - self._port = port - - def cmd_line(self): - return ['None'] - - def poll(self): - return True - - def run_test(self, test_input): - start_time = time.time() - test_name = self._port.relative_test_filename(test_input.filename) - test = self._port._tests[test_name] - if test.keyboard: - raise KeyboardInterrupt - if test.exception: - raise ValueError('exception from ' + test_name) - if test.hang: - time.sleep((float(test_input.timeout) * 4) / 1000.0) - return test_output.TestOutput(test.actual_text, test.actual_image, - test.actual_checksum, test.crash, - time.time() - start_time, test.timeout, - test.error) - - def start(self): - pass - - def stop(self): - pass diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files.py deleted file mode 100644 index 2c0a7b6..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files.py +++ /dev/null @@ -1,128 +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. - -"""This module is used to find all of the layout test files used by -run-webkit-tests. It exposes one public function - find() - -which takes an optional list of paths. If a list is passed in, the returned -list of test files is constrained to those found under the paths passed in, -i.e. calling find(["LayoutTests/fast"]) will only return files -under that directory.""" - -import glob -import os -import time - -from webkitpy.common.system import logutils - - -_log = logutils.get_logger(__file__) - - -# When collecting test cases, we include any file with these extensions. -_supported_file_extensions = set(['.html', '.shtml', '.xml', '.xhtml', '.xhtmlmp', '.pl', - '.php', '.svg']) -# When collecting test cases, skip these directories -_skipped_directories = set(['.svn', '_svn', 'resources', 'script-tests']) - - -def find(port, paths): - """Finds the set of tests under port.layout_tests_dir(). - - Args: - paths: a list of command line paths relative to the layout_tests_dir() - to limit the search to. glob patterns are ok. - """ - gather_start_time = time.time() - paths_to_walk = set() - # if paths is empty, provide a pre-defined list. - if paths: - _log.debug("Gathering tests from: %s relative to %s" % (paths, port.layout_tests_dir())) - for path in paths: - # If there's an * in the name, assume it's a glob pattern. - path = os.path.join(port.layout_tests_dir(), path) - if path.find('*') > -1: - filenames = glob.glob(path) - paths_to_walk.update(filenames) - else: - paths_to_walk.add(path) - else: - _log.debug("Gathering tests from: %s" % port.layout_tests_dir()) - paths_to_walk.add(port.layout_tests_dir()) - - # Now walk all the paths passed in on the command line and get filenames - test_files = set() - for path in paths_to_walk: - if os.path.isfile(path) and _is_test_file(path): - test_files.add(os.path.normpath(path)) - continue - - for root, dirs, files in os.walk(path): - # Don't walk skipped directories or their sub-directories. - if os.path.basename(root) in _skipped_directories: - del dirs[:] - continue - # This copy and for-in is slightly inefficient, but - # the extra walk avoidance consistently shaves .5 seconds - # off of total walk() time on my MacBook Pro. - for directory in dirs[:]: - if directory in _skipped_directories: - dirs.remove(directory) - - for filename in files: - if _is_test_file(filename): - filename = os.path.join(root, filename) - filename = os.path.normpath(filename) - test_files.add(filename) - - gather_time = time.time() - gather_start_time - _log.debug("Test gathering took %f seconds" % gather_time) - - return test_files - - -def _has_supported_extension(filename): - """Return true if filename is one of the file extensions we want to run a - test on.""" - extension = os.path.splitext(filename)[1] - return extension in _supported_file_extensions - - -def _is_reference_html_file(filename): - """Return true if the filename points to a reference HTML file.""" - if (filename.endswith('-expected.html') or - filename.endswith('-expected-mismatch.html')): - _log.warn("Reftests are not supported - ignoring %s" % filename) - return True - return False - - -def _is_test_file(filename): - """Return true if the filename points to a test file.""" - return (_has_supported_extension(filename) and - not _is_reference_html_file(filename)) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py deleted file mode 100644 index 83525c8..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/test_files_unittest.py +++ /dev/null @@ -1,75 +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 unittest - -import base -import test_files - - -class TestFilesTest(unittest.TestCase): - def test_find_no_paths_specified(self): - port = base.Port() - layout_tests_dir = port.layout_tests_dir() - port.layout_tests_dir = lambda: os.path.join(layout_tests_dir, - 'fast', 'html') - tests = test_files.find(port, []) - self.assertNotEqual(tests, 0) - - def test_find_one_test(self): - port = base.Port() - # This is just a test picked at random but known to exist. - tests = test_files.find(port, ['fast/html/keygen.html']) - self.assertEqual(len(tests), 1) - - def test_find_glob(self): - port = base.Port() - tests = test_files.find(port, ['fast/html/key*']) - self.assertEqual(len(tests), 1) - - def test_find_with_skipped_directories(self): - port = base.Port() - tests = port.tests('userscripts') - self.assertTrue('userscripts/resources/frame1.html' not in tests) - - def test_find_with_skipped_directories_2(self): - port = base.Port() - tests = test_files.find(port, ['userscripts/resources']) - self.assertEqual(tests, set([])) - - def test_is_test_file(self): - self.assertTrue(test_files._is_test_file('foo.html')) - self.assertTrue(test_files._is_test_file('foo.shtml')) - self.assertFalse(test_files._is_test_file('foo.png')) - self.assertFalse(test_files._is_test_file('foo-expected.html')) - self.assertFalse(test_files._is_test_file('foo-expected-mismatch.html')) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py deleted file mode 100644 index 09be833..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit.py +++ /dev/null @@ -1,504 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Google Inc. All rights reserved. -# Copyright (C) 2010 Gabor Rapcsanyi <rgabor@inf.u-szeged.hu>, University of Szeged -# -# 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 Google name 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 implementations of the Port interface.""" - - -from __future__ import with_statement - -import codecs -import logging -import os -import re -import shutil -import signal -import sys -import time -import webbrowser -import operator -import tempfile -import shutil - -import webkitpy.common.system.ospath as ospath -import webkitpy.layout_tests.layout_package.test_output as test_output -import webkitpy.layout_tests.port.base as base -import webkitpy.layout_tests.port.server_process as server_process - -_log = logging.getLogger("webkitpy.layout_tests.port.webkit") - - -class WebKitPort(base.Port): - """WebKit implementation of the Port class.""" - - def __init__(self, **kwargs): - base.Port.__init__(self, **kwargs) - self._cached_apache_path = None - - # FIXME: disable pixel tests until they are run by default on the - # build machines. - self.set_option_default('pixel_tests', False) - - def baseline_path(self): - return self._webkit_baseline_path(self._name) - - def baseline_search_path(self): - return [self._webkit_baseline_path(self._name)] - - def path_to_test_expectations_file(self): - return os.path.join(self._webkit_baseline_path(self._name), - 'test_expectations.txt') - - # Only needed by ports which maintain versioned test expectations (like mac-tiger vs. mac-leopard) - def version(self): - return '' - - def _build_driver(self): - configuration = self.get_option('configuration') - return self._config.build_dumprendertree(configuration) - - def _check_driver(self): - driver_path = self._path_to_driver() - if not os.path.exists(driver_path): - _log.error("DumpRenderTree was not found at %s" % driver_path) - return False - return True - - def check_build(self, needs_http): - if self.get_option('build') and not self._build_driver(): - return False - if not self._check_driver(): - return False - if self.get_option('pixel_tests'): - if not self.check_image_diff(): - return False - if not self._check_port_build(): - return False - return True - - def _check_port_build(self): - # Ports can override this method to do additional checks. - return True - - def check_image_diff(self, override_step=None, logging=True): - image_diff_path = self._path_to_image_diff() - if not os.path.exists(image_diff_path): - _log.error("ImageDiff was not found at %s" % image_diff_path) - return False - return True - - def diff_image(self, expected_contents, actual_contents, - diff_filename=None): - """Return True if the two files are different. Also write a delta - image of the two images into |diff_filename| if it is not None.""" - - # Handle the case where the test didn't actually generate an image. - if not actual_contents: - return True - - sp = self._diff_image_request(expected_contents, actual_contents) - return self._diff_image_reply(sp, diff_filename) - - def _diff_image_request(self, expected_contents, actual_contents): - # FIXME: use self.get_option('tolerance') and - # self.set_option_default('tolerance', 0.1) once that behaves correctly - # with default values. - if self.get_option('tolerance') is not None: - tolerance = self.get_option('tolerance') - else: - tolerance = 0.1 - command = [self._path_to_image_diff(), '--tolerance', str(tolerance)] - sp = server_process.ServerProcess(self, 'ImageDiff', command) - - sp.write('Content-Length: %d\n%sContent-Length: %d\n%s' % - (len(actual_contents), actual_contents, - len(expected_contents), expected_contents)) - - return sp - - def _diff_image_reply(self, sp, diff_filename): - timeout = 2.0 - deadline = time.time() + timeout - output = sp.read_line(timeout) - while not sp.timed_out and not sp.crashed and output: - if output.startswith('Content-Length'): - m = re.match('Content-Length: (\d+)', output) - content_length = int(m.group(1)) - timeout = deadline - time.time() - output = sp.read(timeout, content_length) - break - elif output.startswith('diff'): - break - else: - timeout = deadline - time.time() - output = sp.read_line(deadline) - - result = True - if output.startswith('diff'): - m = re.match('diff: (.+)% (passed|failed)', output) - if m.group(2) == 'passed': - result = False - elif output and diff_filename: - with open(diff_filename, 'w') as file: - file.write(output) - elif sp.timed_out: - _log.error("ImageDiff timed out") - elif sp.crashed: - _log.error("ImageDiff crashed") - sp.stop() - return result - - def results_directory(self): - # Results are store relative to the built products to make it easy - # to have multiple copies of webkit checked out and built. - return self._build_path(self.get_option('results_directory')) - - def setup_test_run(self): - # This port doesn't require any specific configuration. - pass - - def create_driver(self, worker_number): - return WebKitDriver(self, worker_number) - - def test_base_platform_names(self): - # At the moment we don't use test platform names, but we have - # to return something. - return ('mac', 'win') - - def _tests_for_other_platforms(self): - raise NotImplementedError('WebKitPort._tests_for_other_platforms') - # The original run-webkit-tests builds up a "whitelist" of tests to - # run, and passes that to DumpRenderTree. new-run-webkit-tests assumes - # we run *all* tests and test_expectations.txt functions as a - # blacklist. - # FIXME: This list could be dynamic based on platform name and - # pushed into base.Port. - return [ - "platform/chromium", - "platform/gtk", - "platform/qt", - "platform/win", - ] - - def _runtime_feature_list(self): - """Return the supported features of DRT. If a port doesn't support - this DRT switch, it has to override this method to return None""" - driver_path = self._path_to_driver() - feature_list = ' '.join(os.popen(driver_path + " --print-supported-features 2>&1").readlines()) - if "SupportedFeatures:" in feature_list: - return feature_list - return None - - def _supported_symbol_list(self): - """Return the supported symbols of WebCore.""" - webcore_library_path = self._path_to_webcore_library() - if not webcore_library_path: - return None - symbol_list = ' '.join(os.popen("nm " + webcore_library_path).readlines()) - return symbol_list - - def _directories_for_features(self): - """Return the supported feature dictionary. The keys are the - features and the values are the directories in lists.""" - directories_for_features = { - "Accelerated Compositing": ["compositing"], - "3D Rendering": ["animations/3d", "transforms/3d"], - } - return directories_for_features - - def _directories_for_symbols(self): - """Return the supported feature dictionary. The keys are the - symbols and the values are the directories in lists.""" - directories_for_symbol = { - "MathMLElement": ["mathml"], - "GraphicsLayer": ["compositing"], - "WebCoreHas3DRendering": ["animations/3d", "transforms/3d"], - "WebGLShader": ["fast/canvas/webgl", "compositing/webgl", "http/tests/canvas/webgl"], - "WMLElement": ["http/tests/wml", "fast/wml", "wml"], - "parseWCSSInputProperty": ["fast/wcss"], - "isXHTMLMPDocument": ["fast/xhtmlmp"], - } - return directories_for_symbol - - def _skipped_tests_for_unsupported_features(self): - """Return the directories of unsupported tests. Search for the - symbols in the symbol_list, if found add the corresponding - directories to the skipped directory list.""" - feature_list = self._runtime_feature_list() - directories = self._directories_for_features() - - # if DRT feature detection not supported - if not feature_list: - feature_list = self._supported_symbol_list() - directories = self._directories_for_symbols() - - if not feature_list: - return [] - - skipped_directories = [directories[feature] - for feature in directories.keys() - if feature not in feature_list] - return reduce(operator.add, skipped_directories) - - def _tests_for_disabled_features(self): - # FIXME: This should use the feature detection from - # webkitperl/features.pm to match run-webkit-tests. - # For now we hard-code a list of features known to be disabled on - # the Mac platform. - disabled_feature_tests = [ - "fast/xhtmlmp", - "http/tests/wml", - "mathml", - "wml", - ] - # FIXME: webarchive tests expect to read-write from - # -expected.webarchive files instead of .txt files. - # This script doesn't know how to do that yet, so pretend they're - # just "disabled". - webarchive_tests = [ - "webarchive", - "svg/webarchive", - "http/tests/webarchive", - "svg/custom/image-with-prefix-in-webarchive.svg", - ] - unsupported_feature_tests = self._skipped_tests_for_unsupported_features() - return disabled_feature_tests + webarchive_tests + unsupported_feature_tests - - def _tests_from_skipped_file(self, skipped_file): - tests_to_skip = [] - for line in skipped_file.readlines(): - line = line.strip() - if line.startswith('#') or not len(line): - continue - tests_to_skip.append(line) - return tests_to_skip - - def _skipped_file_paths(self): - return [os.path.join(self._webkit_baseline_path(self._name), - 'Skipped')] - - def _expectations_from_skipped_files(self): - tests_to_skip = [] - for filename in self._skipped_file_paths(): - if not os.path.exists(filename): - _log.warn("Failed to open Skipped file: %s" % filename) - continue - with codecs.open(filename, "r", "utf-8") as skipped_file: - tests_to_skip.extend(self._tests_from_skipped_file(skipped_file)) - return tests_to_skip - - def test_expectations(self): - # The WebKit mac port uses a combination of a test_expectations file - # and 'Skipped' files. - expectations_path = self.path_to_test_expectations_file() - with codecs.open(expectations_path, "r", "utf-8") as file: - return file.read() + self._skips() - - def _skips(self): - # Each Skipped file contains a list of files - # or directories to be skipped during the test run. The total list - # of tests to skipped is given by the contents of the generic - # Skipped file found in platform/X plus a version-specific file - # found in platform/X-version. Duplicate entries are allowed. - # This routine reads those files and turns contents into the - # format expected by test_expectations. - - tests_to_skip = self.skipped_layout_tests() - skip_lines = map(lambda test_path: "BUG_SKIPPED SKIP : %s = FAIL" % - test_path, tests_to_skip) - return "\n".join(skip_lines) - - def skipped_layout_tests(self): - # Use a set to allow duplicates - tests_to_skip = set(self._expectations_from_skipped_files()) - tests_to_skip.update(self._tests_for_other_platforms()) - tests_to_skip.update(self._tests_for_disabled_features()) - return tests_to_skip - - def test_platform_name(self): - return self._name + self.version() - - def test_platform_names(self): - return self.test_base_platform_names() + ( - 'mac-tiger', 'mac-leopard', 'mac-snowleopard') - - def _build_path(self, *comps): - return self._filesystem.join(self._config.build_directory( - self.get_option('configuration')), *comps) - - def _path_to_driver(self): - return self._build_path('DumpRenderTree') - - def _path_to_webcore_library(self): - return None - - def _path_to_helper(self): - return None - - def _path_to_image_diff(self): - return self._build_path('ImageDiff') - - def _path_to_wdiff(self): - # FIXME: This does not exist on a default Mac OS X Leopard install. - return 'wdiff' - - def _path_to_apache(self): - if not self._cached_apache_path: - # The Apache binary path can vary depending on OS and distribution - # See http://wiki.apache.org/httpd/DistrosDefaultLayout - for path in ["/usr/sbin/httpd", "/usr/sbin/apache2"]: - if os.path.exists(path): - self._cached_apache_path = path - break - - if not self._cached_apache_path: - _log.error("Could not find apache. Not installed or unknown path.") - - return self._cached_apache_path - - -class WebKitDriver(base.Driver): - """WebKit implementation of the DumpRenderTree interface.""" - - def __init__(self, port, worker_number): - self._worker_number = worker_number - self._port = port - self._driver_tempdir = tempfile.mkdtemp(prefix='DumpRenderTree-') - - def __del__(self): - shutil.rmtree(self._driver_tempdir) - - def cmd_line(self): - cmd = self._command_wrapper(self._port.get_option('wrapper')) - cmd += [self._port._path_to_driver(), '-'] - - if self._port.get_option('pixel_tests'): - cmd.append('--pixel-tests') - - if self._port.get_option('use_drt'): - if self._port.get_option('accelerated_compositing'): - cmd.append('--enable-accelerated-compositing') - - if self._port.get_option('accelerated_2d_canvas'): - cmd.append('--enable-accelerated-2d-canvas') - - return cmd - - def start(self): - environment = self._port.setup_environ_for_server() - environment['DYLD_FRAMEWORK_PATH'] = self._port._build_path() - environment['DUMPRENDERTREE_TEMP'] = self._driver_tempdir - self._server_process = server_process.ServerProcess(self._port, - "DumpRenderTree", self.cmd_line(), environment) - - def poll(self): - return self._server_process.poll() - - def restart(self): - self._server_process.stop() - self._server_process.start() - return - - # FIXME: This function is huge. - def run_test(self, test_input): - uri = self._port.filename_to_uri(test_input.filename) - if uri.startswith("file:///"): - command = uri[7:] - else: - command = uri - - if test_input.image_hash: - command += "'" + test_input.image_hash - command += "\n" - - start_time = time.time() - self._server_process.write(command) - - have_seen_content_type = False - actual_image_hash = None - output = str() # Use a byte array for output, even though it should be UTF-8. - image = str() - - timeout = int(test_input.timeout) / 1000.0 - deadline = time.time() + timeout - line = self._server_process.read_line(timeout) - while (not self._server_process.timed_out - and not self._server_process.crashed - and line.rstrip() != "#EOF"): - if (line.startswith('Content-Type:') and not - have_seen_content_type): - have_seen_content_type = True - else: - # Note: Text output from DumpRenderTree is always UTF-8. - # However, some tests (e.g. webarchives) spit out binary - # data instead of text. So to make things simple, we - # always treat the output as binary. - output += line - line = self._server_process.read_line(timeout) - timeout = deadline - time.time() - - # Now read a second block of text for the optional image data - remaining_length = -1 - HASH_HEADER = 'ActualHash: ' - LENGTH_HEADER = 'Content-Length: ' - line = self._server_process.read_line(timeout) - while (not self._server_process.timed_out - and not self._server_process.crashed - and line.rstrip() != "#EOF"): - if line.startswith(HASH_HEADER): - actual_image_hash = line[len(HASH_HEADER):].strip() - elif line.startswith('Content-Type:'): - pass - elif line.startswith(LENGTH_HEADER): - timeout = deadline - time.time() - content_length = int(line[len(LENGTH_HEADER):]) - image = self._server_process.read(timeout, content_length) - timeout = deadline - time.time() - line = self._server_process.read_line(timeout) - - error_lines = self._server_process.error.splitlines() - # FIXME: This is a hack. It is unclear why sometimes - # we do not get any error lines from the server_process - # probably we are not flushing stderr. - if error_lines and error_lines[-1] == "#EOF": - error_lines.pop() # Remove the expected "#EOF" - error = "\n".join(error_lines) - # FIXME: This seems like the wrong section of code to be doing - # this reset in. - self._server_process.error = "" - return test_output.TestOutput(output, image, actual_image_hash, - self._server_process.crashed, - time.time() - start_time, - self._server_process.timed_out, - error) - - def stop(self): - if self._server_process: - self._server_process.stop() - self._server_process = None diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py deleted file mode 100644 index 7b68310..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/webkit_unittest.py +++ /dev/null @@ -1,68 +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 unittest - -from webkitpy.layout_tests.port.webkit import WebKitPort - - -class TestWebKitPort(WebKitPort): - def __init__(self, symbol_list=None, feature_list=None): - self.symbol_list = symbol_list - self.feature_list = feature_list - - def _runtime_feature_list(self): - return self.feature_list - - def _supported_symbol_list(self): - return self.symbol_list - - def _tests_for_other_platforms(self): - return ["media", ] - - def _tests_for_disabled_features(self): - return ["accessibility", ] - - def _skipped_file_paths(self): - return [] - -class WebKitPortTest(unittest.TestCase): - - def test_skipped_directories_for_symbols(self): - supported_symbols = ["GraphicsLayer", "WebCoreHas3DRendering", "isXHTMLMPDocument", "fooSymbol"] - expected_directories = set(["mathml", "fast/canvas/webgl", "compositing/webgl", "http/tests/canvas/webgl", "http/tests/wml", "fast/wml", "wml", "fast/wcss"]) - result_directories = set(TestWebKitPort(supported_symbols, None)._skipped_tests_for_unsupported_features()) - self.assertEqual(result_directories, expected_directories) - - def test_skipped_directories_for_features(self): - supported_features = ["Accelerated Compositing", "Foo Feature"] - expected_directories = set(["animations/3d", "transforms/3d"]) - result_directories = set(TestWebKitPort(None, supported_features)._skipped_tests_for_unsupported_features()) - self.assertEqual(result_directories, expected_directories) - - def test_skipped_layout_tests(self): - self.assertEqual(TestWebKitPort(None, None).skipped_layout_tests(), - set(["media", "accessibility"])) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py deleted file mode 100644 index 926bc04..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py +++ /dev/null @@ -1,257 +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. - -"""A class to help start/stop the PyWebSocket server used by layout tests.""" - - -from __future__ import with_statement - -import codecs -import logging -import optparse -import os -import subprocess -import sys -import tempfile -import time -import urllib - -import factory -import http_server - -from webkitpy.common.system.executive import Executive -from webkitpy.thirdparty.autoinstalled.pywebsocket import mod_pywebsocket - - -_log = logging.getLogger("webkitpy.layout_tests.port.websocket_server") - -_WS_LOG_PREFIX = 'pywebsocket.ws.log-' -_WSS_LOG_PREFIX = 'pywebsocket.wss.log-' - -_DEFAULT_WS_PORT = 8880 -_DEFAULT_WSS_PORT = 9323 - - -def url_is_alive(url): - """Checks to see if we get an http response from |url|. - We poll the url 20 times with a 0.5 second delay. If we don't - get a reply in that time, we give up and assume the httpd - didn't start properly. - - Args: - url: The URL to check. - Return: - True if the url is alive. - """ - sleep_time = 0.5 - wait_time = 10 - while wait_time > 0: - try: - response = urllib.urlopen(url) - # Server is up and responding. - return True - except IOError: - pass - # Wait for sleep_time before trying again. - wait_time -= sleep_time - time.sleep(sleep_time) - - return False - - -class PyWebSocketNotStarted(Exception): - pass - - -class PyWebSocketNotFound(Exception): - pass - - -class PyWebSocket(http_server.Lighttpd): - - def __init__(self, port_obj, output_dir, port=_DEFAULT_WS_PORT, - root=None, use_tls=False, - pidfile=None): - """Args: - output_dir: the absolute path to the layout test result directory - """ - http_server.Lighttpd.__init__(self, port_obj, output_dir, - port=_DEFAULT_WS_PORT, - root=root) - self._output_dir = output_dir - self._process = None - self._port = port - self._root = root - self._use_tls = use_tls - self._private_key = self._pem_file - self._certificate = self._pem_file - if self._port: - self._port = int(self._port) - if self._use_tls: - self._server_name = 'PyWebSocket(Secure)' - else: - self._server_name = 'PyWebSocket' - self._pidfile = pidfile - self._wsout = None - - # Webkit tests - if self._root: - self._layout_tests = os.path.abspath(self._root) - self._web_socket_tests = os.path.abspath( - os.path.join(self._root, 'http', 'tests', - 'websocket', 'tests')) - else: - try: - self._layout_tests = self._port_obj.layout_tests_dir() - self._web_socket_tests = os.path.join(self._layout_tests, - 'http', 'tests', 'websocket', 'tests') - except: - self._web_socket_tests = None - - def start(self): - if not self._web_socket_tests: - _log.info('No need to start %s server.' % self._server_name) - return - if self.is_running(): - raise PyWebSocketNotStarted('%s is already running.' % - self._server_name) - - time_str = time.strftime('%d%b%Y-%H%M%S') - if self._use_tls: - log_prefix = _WSS_LOG_PREFIX - else: - log_prefix = _WS_LOG_PREFIX - log_file_name = log_prefix + time_str - - # Remove old log files. We only need to keep the last ones. - self.remove_log_files(self._output_dir, log_prefix) - - error_log = os.path.join(self._output_dir, log_file_name + "-err.txt") - - output_log = os.path.join(self._output_dir, log_file_name + "-out.txt") - self._wsout = codecs.open(output_log, "w", "utf-8") - - python_interp = sys.executable - pywebsocket_base = os.path.join( - os.path.dirname(os.path.dirname(os.path.dirname( - os.path.abspath(__file__)))), 'thirdparty', - 'autoinstalled', 'pywebsocket') - pywebsocket_script = os.path.join(pywebsocket_base, 'mod_pywebsocket', - 'standalone.py') - start_cmd = [ - python_interp, '-u', pywebsocket_script, - '--server-host', '127.0.0.1', - '--port', str(self._port), - '--document-root', os.path.join(self._layout_tests, 'http', 'tests'), - '--scan-dir', self._web_socket_tests, - '--cgi-paths', '/websocket/tests', - '--log-file', error_log, - ] - - handler_map_file = os.path.join(self._web_socket_tests, - 'handler_map.txt') - if os.path.exists(handler_map_file): - _log.debug('Using handler_map_file: %s' % handler_map_file) - start_cmd.append('--websock-handlers-map-file') - start_cmd.append(handler_map_file) - else: - _log.warning('No handler_map_file found') - - if self._use_tls: - start_cmd.extend(['-t', '-k', self._private_key, - '-c', self._certificate]) - - env = self._port_obj.setup_environ_for_server() - env['PYTHONPATH'] = (pywebsocket_base + os.path.pathsep + - env.get('PYTHONPATH', '')) - - _log.debug('Starting %s server on %d.' % ( - self._server_name, self._port)) - _log.debug('cmdline: %s' % ' '.join(start_cmd)) - # FIXME: We should direct this call through Executive for testing. - # Note: Not thread safe: http://bugs.python.org/issue2320 - self._process = subprocess.Popen(start_cmd, - stdin=open(os.devnull, 'r'), - stdout=self._wsout, - stderr=subprocess.STDOUT, - env=env) - - if self._use_tls: - url = 'https' - else: - url = 'http' - url = url + '://127.0.0.1:%d/' % self._port - if not url_is_alive(url): - if self._process.returncode == None: - # FIXME: We should use a non-static Executive for easier - # testing. - Executive().kill_process(self._process.pid) - with codecs.open(output_log, "r", "utf-8") as fp: - for line in fp: - _log.error(line) - raise PyWebSocketNotStarted( - 'Failed to start %s server on port %s.' % - (self._server_name, self._port)) - - # Our process terminated already - if self._process.returncode != None: - raise PyWebSocketNotStarted( - 'Failed to start %s server.' % self._server_name) - if self._pidfile: - with codecs.open(self._pidfile, "w", "ascii") as file: - file.write("%d" % self._process.pid) - - def stop(self, force=False): - if not force and not self.is_running(): - return - - pid = None - if self._process: - pid = self._process.pid - elif self._pidfile: - with codecs.open(self._pidfile, "r", "ascii") as file: - pid = int(file.read().strip()) - - if not pid: - raise PyWebSocketNotFound( - 'Failed to find %s server pid.' % self._server_name) - - _log.debug('Shutting down %s server %d.' % (self._server_name, pid)) - # FIXME: We should use a non-static Executive for easier testing. - Executive().kill_process(pid) - - if self._process: - # wait() is not threadsafe and can throw OSError due to: - # http://bugs.python.org/issue1731717 - self._process.wait() - self._process = None - - if self._wsout: - self._wsout.close() - self._wsout = None diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py deleted file mode 100644 index 9e30155..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/win.py +++ /dev/null @@ -1,75 +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 Google name 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 Win implementation of the Port interface.""" - -import logging -import os - -from webkitpy.layout_tests.port.webkit import WebKitPort - -_log = logging.getLogger("webkitpy.layout_tests.port.win") - - -class WinPort(WebKitPort): - """WebKit Win implementation of the Port class.""" - - def __init__(self, **kwargs): - kwargs.setdefault('port_name', 'win') - WebKitPort.__init__(self, **kwargs) - - def baseline_search_path(self): - # Based on code from old-run-webkit-tests expectedDirectoryForTest() - port_names = ["win", "mac-snowleopard", "mac"] - return map(self._webkit_baseline_path, port_names) - - def _tests_for_other_platforms(self): - # FIXME: This list could be dynamic based on platform name and - # pushed into base.Port. - # This really need to be automated. - return [ - "platform/chromium", - "platform/gtk", - "platform/qt", - "platform/mac", - ] - - def _path_to_apache_config_file(self): - return os.path.join(self.layout_tests_dir(), 'http', 'conf', - 'cygwin-httpd.conf') - - def _shut_down_http_server(self, server_pid): - """Shut down the httpd web server. Blocks until it's fully - shut down. - - Args: - server_pid: The process ID of the running server. - """ - # Looks like we ignore server_pid. - # Copy/pasted from chromium-win. - self._executive.kill_all("httpd.exe") diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py deleted file mode 100644 index 55c4558..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests.py +++ /dev/null @@ -1,966 +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. - -"""Rebaselining tool that automatically produces baselines for all platforms. - -The script does the following for each platform specified: - 1. Compile a list of tests that need rebaselining. - 2. Download test result archive from buildbot for the platform. - 3. Extract baselines from the archive file for all identified files. - 4. Add new baselines to SVN repository. - 5. For each test that has been rebaselined, remove this platform option from - the test in test_expectation.txt. If no other platforms remain after - removal, delete the rebaselined test from the file. - -At the end, the script generates a html that compares old and new baselines. -""" - -from __future__ import with_statement - -import codecs -import copy -import logging -import optparse -import os -import re -import shutil -import subprocess -import sys -import tempfile -import time -import urllib -import zipfile - -from webkitpy.common.system import path -from webkitpy.common.system import user -from webkitpy.common.system.executive import Executive, ScriptError -import webkitpy.common.checkout.scm as scm - -import port -from layout_package import test_expectations - -_log = logging.getLogger("webkitpy.layout_tests." - "rebaseline_chromium_webkit_tests") - -BASELINE_SUFFIXES = ['.txt', '.png', '.checksum'] -REBASELINE_PLATFORM_ORDER = ['mac', 'win', 'win-xp', 'win-vista', 'linux'] -ARCHIVE_DIR_NAME_DICT = {'win': 'Webkit_Win', - 'win-vista': 'webkit-dbg-vista', - 'win-xp': 'Webkit_Win', - 'mac': 'Webkit_Mac10_5', - 'linux': 'webkit-rel-linux64', - 'win-canary': 'webkit-rel-webkit-org', - 'win-vista-canary': 'webkit-dbg-vista', - 'win-xp-canary': 'webkit-rel-webkit-org', - 'mac-canary': 'webkit-rel-mac-webkit-org', - 'linux-canary': 'webkit-rel-linux-webkit-org'} - - -def log_dashed_string(text, platform, logging_level=logging.INFO): - """Log text message with dashes on both sides.""" - - msg = text - if platform: - msg += ': ' + platform - if len(msg) < 78: - dashes = '-' * ((78 - len(msg)) / 2) - msg = '%s %s %s' % (dashes, msg, dashes) - - if logging_level == logging.ERROR: - _log.error(msg) - elif logging_level == logging.WARNING: - _log.warn(msg) - else: - _log.info(msg) - - -def setup_html_directory(html_directory): - """Setup the directory to store html results. - - All html related files are stored in the "rebaseline_html" subdirectory. - - Args: - html_directory: parent directory that stores the rebaselining results. - If None, a temp directory is created. - - Returns: - the directory that stores the html related rebaselining results. - """ - - if not html_directory: - html_directory = tempfile.mkdtemp() - elif not os.path.exists(html_directory): - os.mkdir(html_directory) - - html_directory = os.path.join(html_directory, 'rebaseline_html') - _log.info('Html directory: "%s"', html_directory) - - if os.path.exists(html_directory): - shutil.rmtree(html_directory, True) - _log.info('Deleted file at html directory: "%s"', html_directory) - - if not os.path.exists(html_directory): - os.mkdir(html_directory) - return html_directory - - -def get_result_file_fullpath(html_directory, baseline_filename, platform, - result_type): - """Get full path of the baseline result file. - - Args: - html_directory: directory that stores the html related files. - baseline_filename: name of the baseline file. - platform: win, linux or mac - result_type: type of the baseline result: '.txt', '.png'. - - Returns: - Full path of the baseline file for rebaselining result comparison. - """ - - base, ext = os.path.splitext(baseline_filename) - result_filename = '%s-%s-%s%s' % (base, platform, result_type, ext) - fullpath = os.path.join(html_directory, result_filename) - _log.debug(' Result file full path: "%s".', fullpath) - return fullpath - - -class Rebaseliner(object): - """Class to produce new baselines for a given platform.""" - - REVISION_REGEX = r'<a href=\"(\d+)/\">' - - def __init__(self, running_port, target_port, platform, options): - """ - Args: - running_port: the Port the script is running on. - target_port: the Port the script uses to find port-specific - configuration information like the test_expectations.txt - file location and the list of test platforms. - platform: the test platform to rebaseline - options: the command-line options object.""" - self._platform = platform - self._options = options - self._port = running_port - self._target_port = target_port - self._rebaseline_port = port.get( - self._target_port.test_platform_name_to_name(platform), options) - self._rebaselining_tests = [] - self._rebaselined_tests = [] - - # Create tests and expectations helper which is used to: - # -. compile list of tests that need rebaselining. - # -. update the tests in test_expectations file after rebaseline - # is done. - expectations_str = self._rebaseline_port.test_expectations() - self._test_expectations = \ - test_expectations.TestExpectations(self._rebaseline_port, - None, - expectations_str, - self._platform, - False, - False) - self._scm = scm.default_scm() - - def run(self, backup): - """Run rebaseline process.""" - - log_dashed_string('Compiling rebaselining tests', self._platform) - if not self._compile_rebaselining_tests(): - return True - - log_dashed_string('Downloading archive', self._platform) - archive_file = self._download_buildbot_archive() - _log.info('') - if not archive_file: - _log.error('No archive found.') - return False - - log_dashed_string('Extracting and adding new baselines', - self._platform) - if not self._extract_and_add_new_baselines(archive_file): - return False - - log_dashed_string('Updating rebaselined tests in file', - self._platform) - self._update_rebaselined_tests_in_file(backup) - _log.info('') - - if len(self._rebaselining_tests) != len(self._rebaselined_tests): - _log.warning('NOT ALL TESTS THAT NEED REBASELINING HAVE BEEN ' - 'REBASELINED.') - _log.warning(' Total tests needing rebaselining: %d', - len(self._rebaselining_tests)) - _log.warning(' Total tests rebaselined: %d', - len(self._rebaselined_tests)) - return False - - _log.warning('All tests needing rebaselining were successfully ' - 'rebaselined.') - - return True - - def get_rebaselining_tests(self): - return self._rebaselining_tests - - def _compile_rebaselining_tests(self): - """Compile list of tests that need rebaselining for the platform. - - Returns: - List of tests that need rebaselining or - None if there is no such test. - """ - - self._rebaselining_tests = \ - self._test_expectations.get_rebaselining_failures() - if not self._rebaselining_tests: - _log.warn('No tests found that need rebaselining.') - return None - - _log.info('Total number of tests needing rebaselining ' - 'for "%s": "%d"', self._platform, - len(self._rebaselining_tests)) - - test_no = 1 - for test in self._rebaselining_tests: - _log.info(' %d: %s', test_no, test) - test_no += 1 - - return self._rebaselining_tests - - def _get_latest_revision(self, url): - """Get the latest layout test revision number from buildbot. - - Args: - url: Url to retrieve layout test revision numbers. - - Returns: - latest revision or - None on failure. - """ - - _log.debug('Url to retrieve revision: "%s"', url) - - f = urllib.urlopen(url) - content = f.read() - f.close() - - revisions = re.findall(self.REVISION_REGEX, content) - if not revisions: - _log.error('Failed to find revision, content: "%s"', content) - return None - - revisions.sort(key=int) - _log.info('Latest revision: "%s"', revisions[len(revisions) - 1]) - return revisions[len(revisions) - 1] - - def _get_archive_dir_name(self, platform, webkit_canary): - """Get name of the layout test archive directory. - - Returns: - Directory name or - None on failure - """ - - if webkit_canary: - platform += '-canary' - - if platform in ARCHIVE_DIR_NAME_DICT: - return ARCHIVE_DIR_NAME_DICT[platform] - else: - _log.error('Cannot find platform key %s in archive ' - 'directory name dictionary', platform) - return None - - def _get_archive_url(self): - """Generate the url to download latest layout test archive. - - Returns: - Url to download archive or - None on failure - """ - - if self._options.force_archive_url: - return self._options.force_archive_url - - dir_name = self._get_archive_dir_name(self._platform, - self._options.webkit_canary) - if not dir_name: - return None - - _log.debug('Buildbot platform dir name: "%s"', dir_name) - - url_base = '%s/%s/' % (self._options.archive_url, dir_name) - latest_revision = self._get_latest_revision(url_base) - if latest_revision is None or latest_revision <= 0: - return None - archive_url = ('%s%s/layout-test-results.zip' % (url_base, - latest_revision)) - _log.info('Archive url: "%s"', archive_url) - return archive_url - - def _download_buildbot_archive(self): - """Download layout test archive file from buildbot. - - Returns: - True if download succeeded or - False otherwise. - """ - - url = self._get_archive_url() - if url is None: - return None - - fn = urllib.urlretrieve(url)[0] - _log.info('Archive downloaded and saved to file: "%s"', fn) - return fn - - def _extract_and_add_new_baselines(self, archive_file): - """Extract new baselines from archive and add them to SVN repository. - - Args: - archive_file: full path to the archive file. - - Returns: - List of tests that have been rebaselined or - None on failure. - """ - - zip_file = zipfile.ZipFile(archive_file, 'r') - zip_namelist = zip_file.namelist() - - _log.debug('zip file namelist:') - for name in zip_namelist: - _log.debug(' ' + name) - - platform = self._rebaseline_port.test_platform_name_to_name( - self._platform) - _log.debug('Platform dir: "%s"', platform) - - test_no = 1 - self._rebaselined_tests = [] - for test in self._rebaselining_tests: - _log.info('Test %d: %s', test_no, test) - - found = False - scm_error = False - test_basename = os.path.splitext(test)[0] - for suffix in BASELINE_SUFFIXES: - archive_test_name = ('layout-test-results/%s-actual%s' % - (test_basename, suffix)) - _log.debug(' Archive test file name: "%s"', - archive_test_name) - if not archive_test_name in zip_namelist: - _log.info(' %s file not in archive.', suffix) - continue - - found = True - _log.info(' %s file found in archive.', suffix) - - # Extract new baseline from archive and save it to a temp file. - data = zip_file.read(archive_test_name) - temp_fd, temp_name = tempfile.mkstemp(suffix) - f = os.fdopen(temp_fd, 'wb') - f.write(data) - f.close() - - expected_filename = '%s-expected%s' % (test_basename, suffix) - expected_fullpath = os.path.join( - self._rebaseline_port.baseline_path(), expected_filename) - expected_fullpath = os.path.normpath(expected_fullpath) - _log.debug(' Expected file full path: "%s"', - expected_fullpath) - - # TODO(victorw): for now, the rebaselining tool checks whether - # or not THIS baseline is duplicate and should be skipped. - # We could improve the tool to check all baselines in upper - # and lower - # levels and remove all duplicated baselines. - if self._is_dup_baseline(temp_name, - expected_fullpath, - test, - suffix, - self._platform): - os.remove(temp_name) - self._delete_baseline(expected_fullpath) - continue - - # Create the new baseline directory if it doesn't already - # exist. - self._port.maybe_make_directory( - os.path.dirname(expected_fullpath)) - - shutil.move(temp_name, expected_fullpath) - - if 0 != self._scm.add(expected_fullpath, return_exit_code=True): - # FIXME: print detailed diagnose messages - scm_error = True - elif suffix != '.checksum': - self._create_html_baseline_files(expected_fullpath) - - if not found: - _log.warn(' No new baselines found in archive.') - else: - if scm_error: - _log.warn(' Failed to add baselines to your repository.') - else: - _log.info(' Rebaseline succeeded.') - self._rebaselined_tests.append(test) - - test_no += 1 - - zip_file.close() - os.remove(archive_file) - - return self._rebaselined_tests - - def _is_dup_baseline(self, new_baseline, baseline_path, test, suffix, - platform): - """Check whether a baseline is duplicate and can fallback to same - baseline for another platform. For example, if a test has same - baseline on linux and windows, then we only store windows - baseline and linux baseline will fallback to the windows version. - - Args: - expected_filename: baseline expectation file name. - test: test name. - suffix: file suffix of the expected results, including dot; - e.g. '.txt' or '.png'. - platform: baseline platform 'mac', 'win' or 'linux'. - - Returns: - True if the baseline is unnecessary. - False otherwise. - """ - test_filepath = os.path.join(self._target_port.layout_tests_dir(), - test) - all_baselines = self._rebaseline_port.expected_baselines( - test_filepath, suffix, True) - for (fallback_dir, fallback_file) in all_baselines: - if fallback_dir and fallback_file: - fallback_fullpath = os.path.normpath( - os.path.join(fallback_dir, fallback_file)) - if fallback_fullpath.lower() != baseline_path.lower(): - with codecs.open(new_baseline, "r", - None) as file_handle1: - new_output = file_handle1.read() - with codecs.open(fallback_fullpath, "r", - None) as file_handle2: - fallback_output = file_handle2.read() - is_image = baseline_path.lower().endswith('.png') - if not self._diff_baselines(new_output, fallback_output, - is_image): - _log.info(' Found same baseline at %s', - fallback_fullpath) - return True - else: - return False - - return False - - def _diff_baselines(self, output1, output2, is_image): - """Check whether two baselines are different. - - Args: - output1, output2: contents of the baselines to compare. - - Returns: - True if two files are different or have different extensions. - False otherwise. - """ - - if is_image: - return self._port.diff_image(output1, output2, None) - else: - return self._port.compare_text(output1, output2) - - def _delete_baseline(self, filename): - """Remove the file from repository and delete it from disk. - - Args: - filename: full path of the file to delete. - """ - - if not filename or not os.path.isfile(filename): - return - self._scm.delete(filename) - - def _update_rebaselined_tests_in_file(self, backup): - """Update the rebaselined tests in test expectations file. - - Args: - backup: if True, backup the original test expectations file. - - Returns: - no - """ - - if self._rebaselined_tests: - new_expectations = ( - self._test_expectations.remove_platform_from_expectations( - self._rebaselined_tests, self._platform)) - path = self._target_port.path_to_test_expectations_file() - if backup: - date_suffix = time.strftime('%Y%m%d%H%M%S', - time.localtime(time.time())) - backup_file = ('%s.orig.%s' % (path, date_suffix)) - if os.path.exists(backup_file): - os.remove(backup_file) - _log.info('Saving original file to "%s"', backup_file) - os.rename(path, backup_file) - # FIXME: What encoding are these files? - # Or is new_expectations always a byte array? - with open(path, "w") as file: - file.write(new_expectations) - # self._scm.add(path) - else: - _log.info('No test was rebaselined so nothing to remove.') - - def _create_html_baseline_files(self, baseline_fullpath): - """Create baseline files (old, new and diff) in html directory. - - The files are used to compare the rebaselining results. - - Args: - baseline_fullpath: full path of the expected baseline file. - """ - - if not baseline_fullpath or not os.path.exists(baseline_fullpath): - return - - # Copy the new baseline to html directory for result comparison. - baseline_filename = os.path.basename(baseline_fullpath) - new_file = get_result_file_fullpath(self._options.html_directory, - baseline_filename, self._platform, - 'new') - shutil.copyfile(baseline_fullpath, new_file) - _log.info(' Html: copied new baseline file from "%s" to "%s".', - baseline_fullpath, new_file) - - # Get the old baseline from the repository and save to the html directory. - try: - output = self._scm.show_head(baseline_fullpath) - except ScriptError, e: - _log.info(e) - output = "" - - if (not output) or (output.upper().rstrip().endswith( - 'NO SUCH FILE OR DIRECTORY')): - _log.info(' No base file: "%s"', baseline_fullpath) - return - base_file = get_result_file_fullpath(self._options.html_directory, - baseline_filename, self._platform, - 'old') - # We should be using an explicit encoding here. - with open(base_file, "wb") as file: - file.write(output) - _log.info(' Html: created old baseline file: "%s".', - base_file) - - # Get the diff between old and new baselines and save to the html dir. - if baseline_filename.upper().endswith('.TXT'): - output = self._scm.diff_for_file(baseline_fullpath, log=_log) - if output: - diff_file = get_result_file_fullpath( - self._options.html_directory, baseline_filename, - self._platform, 'diff') - with open(diff_file, 'wb') as file: - file.write(output) - _log.info(' Html: created baseline diff file: "%s".', - diff_file) - - -class HtmlGenerator(object): - """Class to generate rebaselining result comparison html.""" - - HTML_REBASELINE = ('<html>' - '<head>' - '<style>' - 'body {font-family: sans-serif;}' - '.mainTable {background: #666666;}' - '.mainTable td , .mainTable th {background: white;}' - '.detail {margin-left: 10px; margin-top: 3px;}' - '</style>' - '<title>Rebaselining Result Comparison (%(time)s)' - '</title>' - '</head>' - '<body>' - '<h2>Rebaselining Result Comparison (%(time)s)</h2>' - '%(body)s' - '</body>' - '</html>') - HTML_NO_REBASELINING_TESTS = ( - '<p>No tests found that need rebaselining.</p>') - HTML_TABLE_TEST = ('<table class="mainTable" cellspacing=1 cellpadding=5>' - '%s</table><br>') - HTML_TR_TEST = ('<tr>' - '<th style="background-color: #CDECDE; border-bottom: ' - '1px solid black; font-size: 18pt; font-weight: bold" ' - 'colspan="5">' - '<a href="%s">%s</a>' - '</th>' - '</tr>') - HTML_TEST_DETAIL = ('<div class="detail">' - '<tr>' - '<th width="100">Baseline</th>' - '<th width="100">Platform</th>' - '<th width="200">Old</th>' - '<th width="200">New</th>' - '<th width="150">Difference</th>' - '</tr>' - '%s' - '</div>') - HTML_TD_NOLINK = '<td align=center><a>%s</a></td>' - HTML_TD_LINK = '<td align=center><a href="%(uri)s">%(name)s</a></td>' - HTML_TD_LINK_IMG = ('<td><a href="%(uri)s">' - '<img style="width: 200" src="%(uri)s" /></a></td>') - HTML_TR = '<tr>%s</tr>' - - def __init__(self, target_port, options, platforms, rebaselining_tests, - executive): - self._html_directory = options.html_directory - self._target_port = target_port - self._platforms = platforms - self._rebaselining_tests = rebaselining_tests - self._executive = executive - self._html_file = os.path.join(options.html_directory, - 'rebaseline.html') - - def abspath_to_uri(self, filename): - """Converts an absolute path to a file: URI.""" - return path.abspath_to_uri(filename, self._executive) - - def generate_html(self): - """Generate html file for rebaselining result comparison.""" - - _log.info('Generating html file') - - html_body = '' - if not self._rebaselining_tests: - html_body += self.HTML_NO_REBASELINING_TESTS - else: - tests = list(self._rebaselining_tests) - tests.sort() - - test_no = 1 - for test in tests: - _log.info('Test %d: %s', test_no, test) - html_body += self._generate_html_for_one_test(test) - - html = self.HTML_REBASELINE % ({'time': time.asctime(), - 'body': html_body}) - _log.debug(html) - - with codecs.open(self._html_file, "w", "utf-8") as file: - file.write(html) - - _log.info('Baseline comparison html generated at "%s"', - self._html_file) - - def show_html(self): - """Launch the rebaselining html in brwoser.""" - - _log.info('Launching html: "%s"', self._html_file) - user.User().open_url(self._html_file) - _log.info('Html launched.') - - def _generate_baseline_links(self, test_basename, suffix, platform): - """Generate links for baseline results (old, new and diff). - - Args: - test_basename: base filename of the test - suffix: baseline file suffixes: '.txt', '.png' - platform: win, linux or mac - - Returns: - html links for showing baseline results (old, new and diff) - """ - - baseline_filename = '%s-expected%s' % (test_basename, suffix) - _log.debug(' baseline filename: "%s"', baseline_filename) - - new_file = get_result_file_fullpath(self._html_directory, - baseline_filename, platform, 'new') - _log.info(' New baseline file: "%s"', new_file) - if not os.path.exists(new_file): - _log.info(' No new baseline file: "%s"', new_file) - return '' - - old_file = get_result_file_fullpath(self._html_directory, - baseline_filename, platform, 'old') - _log.info(' Old baseline file: "%s"', old_file) - if suffix == '.png': - html_td_link = self.HTML_TD_LINK_IMG - else: - html_td_link = self.HTML_TD_LINK - - links = '' - if os.path.exists(old_file): - links += html_td_link % { - 'uri': self.abspath_to_uri(old_file), - 'name': baseline_filename} - else: - _log.info(' No old baseline file: "%s"', old_file) - links += self.HTML_TD_NOLINK % '' - - links += html_td_link % {'uri': self.abspath_to_uri(new_file), - 'name': baseline_filename} - - diff_file = get_result_file_fullpath(self._html_directory, - baseline_filename, platform, - 'diff') - _log.info(' Baseline diff file: "%s"', diff_file) - if os.path.exists(diff_file): - links += html_td_link % {'uri': self.abspath_to_uri(diff_file), - 'name': 'Diff'} - else: - _log.info(' No baseline diff file: "%s"', diff_file) - links += self.HTML_TD_NOLINK % '' - - return links - - def _generate_html_for_one_test(self, test): - """Generate html for one rebaselining test. - - Args: - test: layout test name - - Returns: - html that compares baseline results for the test. - """ - - test_basename = os.path.basename(os.path.splitext(test)[0]) - _log.info(' basename: "%s"', test_basename) - rows = [] - for suffix in BASELINE_SUFFIXES: - if suffix == '.checksum': - continue - - _log.info(' Checking %s files', suffix) - for platform in self._platforms: - links = self._generate_baseline_links(test_basename, suffix, - platform) - if links: - row = self.HTML_TD_NOLINK % self._get_baseline_result_type( - suffix) - row += self.HTML_TD_NOLINK % platform - row += links - _log.debug(' html row: %s', row) - - rows.append(self.HTML_TR % row) - - if rows: - test_path = os.path.join(self._target_port.layout_tests_dir(), - test) - html = self.HTML_TR_TEST % (self.abspath_to_uri(test_path), test) - html += self.HTML_TEST_DETAIL % ' '.join(rows) - - _log.debug(' html for test: %s', html) - return self.HTML_TABLE_TEST % html - - return '' - - def _get_baseline_result_type(self, suffix): - """Name of the baseline result type.""" - - if suffix == '.png': - return 'Pixel' - elif suffix == '.txt': - return 'Render Tree' - else: - return 'Other' - - -def get_host_port_object(options): - """Return a port object for the platform we're running on.""" - # The only thing we really need on the host is a way to diff - # text files and image files, which means we need to check that some - # version of ImageDiff has been built. We will look for either Debug - # or Release versions of the default port on the platform. - options.configuration = "Release" - port_obj = port.get(None, options) - if not port_obj.check_image_diff(override_step=None, logging=False): - _log.debug('No release version of the image diff binary was found.') - options.configuration = "Debug" - port_obj = port.get(None, options) - if not port_obj.check_image_diff(override_step=None, logging=False): - _log.error('No version of image diff was found. Check your build.') - return None - else: - _log.debug('Found the debug version of the image diff binary.') - else: - _log.debug('Found the release version of the image diff binary.') - return port_obj - - -def parse_options(args): - """Parse options and return a pair of host options and target options.""" - option_parser = optparse.OptionParser() - option_parser.add_option('-v', '--verbose', - action='store_true', - default=False, - help='include debug-level logging.') - - option_parser.add_option('-q', '--quiet', - action='store_true', - help='Suppress result HTML viewing') - - option_parser.add_option('-p', '--platforms', - default='mac,win,win-xp,win-vista,linux', - help=('Comma delimited list of platforms ' - 'that need rebaselining.')) - - option_parser.add_option('-u', '--archive_url', - default=('http://build.chromium.org/buildbot/' - 'layout_test_results'), - help=('Url to find the layout test result archive' - ' file.')) - option_parser.add_option('-U', '--force_archive_url', - help=('Url of result zip file. This option is for debugging ' - 'purposes')) - - option_parser.add_option('-w', '--webkit_canary', - action='store_true', - default=False, - help=('If True, pull baselines from webkit.org ' - 'canary bot.')) - - option_parser.add_option('-b', '--backup', - action='store_true', - default=False, - help=('Whether or not to backup the original test' - ' expectations file after rebaseline.')) - - option_parser.add_option('-d', '--html_directory', - default='', - help=('The directory that stores the results for ' - 'rebaselining comparison.')) - - option_parser.add_option('', '--use_drt', - action='store_true', - default=False, - help=('Use ImageDiff from DumpRenderTree instead ' - 'of image_diff for pixel tests.')) - - option_parser.add_option('', '--target-platform', - default='chromium', - help=('The target platform to rebaseline ' - '("mac", "chromium", "qt", etc.). Defaults ' - 'to "chromium".')) - options = option_parser.parse_args(args)[0] - - target_options = copy.copy(options) - if options.target_platform == 'chromium': - target_options.chromium = True - options.tolerance = 0 - - return (options, target_options) - - -def main(executive=Executive()): - """Main function to produce new baselines.""" - - (options, target_options) = parse_options(sys.argv[1:]) - - # We need to create three different Port objects over the life of this - # script. |target_port_obj| is used to determine configuration information: - # location of the expectations file, names of ports to rebaseline, etc. - # |port_obj| is used for runtime functionality like actually diffing - # Then we create a rebaselining port to actual find and manage the - # baselines. - target_port_obj = port.get(None, target_options) - - # Set up our logging format. - log_level = logging.INFO - if options.verbose: - log_level = logging.DEBUG - logging.basicConfig(level=log_level, - format=('%(asctime)s %(filename)s:%(lineno)-3d ' - '%(levelname)s %(message)s'), - datefmt='%y%m%d %H:%M:%S') - - host_port_obj = get_host_port_object(options) - if not host_port_obj: - sys.exit(1) - - # Verify 'platforms' option is valid. - if not options.platforms: - _log.error('Invalid "platforms" option. --platforms must be ' - 'specified in order to rebaseline.') - sys.exit(1) - platforms = [p.strip().lower() for p in options.platforms.split(',')] - for platform in platforms: - if not platform in REBASELINE_PLATFORM_ORDER: - _log.error('Invalid platform: "%s"' % (platform)) - sys.exit(1) - - # Adjust the platform order so rebaseline tool is running at the order of - # 'mac', 'win' and 'linux'. This is in same order with layout test baseline - # search paths. It simplifies how the rebaseline tool detects duplicate - # baselines. Check _IsDupBaseline method for details. - rebaseline_platforms = [] - for platform in REBASELINE_PLATFORM_ORDER: - if platform in platforms: - rebaseline_platforms.append(platform) - - options.html_directory = setup_html_directory(options.html_directory) - - rebaselining_tests = set() - backup = options.backup - for platform in rebaseline_platforms: - rebaseliner = Rebaseliner(host_port_obj, target_port_obj, - platform, options) - - _log.info('') - log_dashed_string('Rebaseline started', platform) - if rebaseliner.run(backup): - # Only need to backup one original copy of test expectation file. - backup = False - log_dashed_string('Rebaseline done', platform) - else: - log_dashed_string('Rebaseline failed', platform, logging.ERROR) - - rebaselining_tests |= set(rebaseliner.get_rebaselining_tests()) - - _log.info('') - log_dashed_string('Rebaselining result comparison started', None) - html_generator = HtmlGenerator(target_port_obj, - options, - rebaseline_platforms, - rebaselining_tests, - executive=executive) - html_generator.generate_html() - if not options.quiet: - html_generator.show_html() - log_dashed_string('Rebaselining result comparison done', None) - - sys.exit(0) - -if '__main__' == __name__: - main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py deleted file mode 100644 index 7c55b94..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/rebaseline_chromium_webkit_tests_unittest.py +++ /dev/null @@ -1,157 +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 rebaseline_chromium_webkit_tests.py.""" - -import os -import sys -import unittest - -from webkitpy.tool import mocktool -from webkitpy.layout_tests import port -from webkitpy.layout_tests import rebaseline_chromium_webkit_tests -from webkitpy.common.system.executive import Executive, ScriptError - - -class MockPort(object): - def __init__(self, image_diff_exists): - self.image_diff_exists = image_diff_exists - - def check_image_diff(self, override_step, logging): - return self.image_diff_exists - - -def get_mock_get(config_expectations): - def mock_get(port_name, options): - return MockPort(config_expectations[options.configuration]) - return mock_get - - -class TestGetHostPortObject(unittest.TestCase): - def assert_result(self, release_present, debug_present, valid_port_obj): - # Tests whether we get a valid port object returned when we claim - # that Image diff is (or isn't) present in the two configs. - port.get = get_mock_get({'Release': release_present, - 'Debug': debug_present}) - options = mocktool.MockOptions(configuration=None, - html_directory=None) - port_obj = rebaseline_chromium_webkit_tests.get_host_port_object( - options) - if valid_port_obj: - self.assertNotEqual(port_obj, None) - else: - self.assertEqual(port_obj, None) - - def test_get_host_port_object(self): - # Save the normal port.get() function for future testing. - old_get = port.get - - # Test whether we get a valid port object back for the four - # possible cases of having ImageDiffs built. It should work when - # there is at least one binary present. - self.assert_result(False, False, False) - self.assert_result(True, False, True) - self.assert_result(False, True, True) - self.assert_result(True, True, True) - - # Restore the normal port.get() function. - port.get = old_get - - -class TestRebaseliner(unittest.TestCase): - def make_rebaseliner(self): - options = mocktool.MockOptions(configuration=None, - html_directory=None) - host_port_obj = port.get('test', options) - target_options = options - target_port_obj = port.get('test', target_options) - platform = 'test' - return rebaseline_chromium_webkit_tests.Rebaseliner( - host_port_obj, target_port_obj, platform, options) - - def test_parse_options(self): - (options, target_options) = rebaseline_chromium_webkit_tests.parse_options([]) - self.assertTrue(target_options.chromium) - self.assertEqual(options.tolerance, 0) - - (options, target_options) = rebaseline_chromium_webkit_tests.parse_options(['--target-platform', 'qt']) - self.assertFalse(hasattr(target_options, 'chromium')) - self.assertEqual(options.tolerance, 0) - - def test_noop(self): - # this method tests that was can at least instantiate an object, even - # if there is nothing to do. - rebaseliner = self.make_rebaseliner() - self.assertNotEqual(rebaseliner, None) - - def test_diff_baselines_txt(self): - rebaseliner = self.make_rebaseliner() - output = rebaseliner._port.expected_text( - os.path.join(rebaseliner._port.layout_tests_dir(), - 'passes/text.html')) - self.assertFalse(rebaseliner._diff_baselines(output, output, - is_image=False)) - - def test_diff_baselines_png(self): - rebaseliner = self.make_rebaseliner() - image = rebaseliner._port.expected_image( - os.path.join(rebaseliner._port.layout_tests_dir(), - 'passes/image.html')) - self.assertFalse(rebaseliner._diff_baselines(image, image, - is_image=True)) - - -class TestHtmlGenerator(unittest.TestCase): - def make_generator(self, tests): - return rebaseline_chromium_webkit_tests.HtmlGenerator( - target_port=None, - options=mocktool.MockOptions(configuration=None, - html_directory='/tmp'), - platforms=['mac'], - rebaselining_tests=tests, - executive=Executive()) - - def test_generate_baseline_links(self): - orig_platform = sys.platform - orig_exists = os.path.exists - - try: - sys.platform = 'darwin' - os.path.exists = lambda x: True - generator = self.make_generator(["foo.txt"]) - links = generator._generate_baseline_links("foo", ".txt", "mac") - expected_links = '<td align=center><a href="file:///tmp/foo-expected-mac-old.txt">foo-expected.txt</a></td><td align=center><a href="file:///tmp/foo-expected-mac-new.txt">foo-expected.txt</a></td><td align=center><a href="file:///tmp/foo-expected-mac-diff.txt">Diff</a></td>' - self.assertEqual(links, expected_links) - finally: - sys.platform = orig_platform - os.path.exists = orig_exists - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py deleted file mode 100755 index 643e204..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests.py +++ /dev/null @@ -1,1634 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2010 Google Inc. All rights reserved. -# Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged -# -# 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. - -"""Run layout tests. - -This is a port of the existing webkit test script run-webkit-tests. - -The TestRunner class runs a series of tests (TestType interface) against a set -of test files. If a test file fails a TestType, it returns a list TestFailure -objects to the TestRunner. The TestRunner then aggregates the TestFailures to -create a final report. - -This script reads several files, if they exist in the test_lists subdirectory -next to this script itself. Each should contain a list of paths to individual -tests or entire subdirectories of tests, relative to the outermost test -directory. Entire lines starting with '//' (comments) will be ignored. - -For details of the files' contents and purposes, see test_lists/README. -""" - -from __future__ import with_statement - -import codecs -import errno -import glob -import logging -import math -import optparse -import os -import platform -import Queue -import random -import re -import shutil -import signal -import sys -import time -import traceback - -from layout_package import dump_render_tree_thread -from layout_package import json_layout_results_generator -from layout_package import message_broker -from layout_package import printing -from layout_package import test_expectations -from layout_package import test_failures -from layout_package import test_results -from layout_package import test_results_uploader - -from webkitpy.common.system import user -from webkitpy.thirdparty import simplejson -from webkitpy.tool import grammar - -import port - -_log = logging.getLogger("webkitpy.layout_tests.run_webkit_tests") - -# Builder base URL where we have the archived test results. -BUILDER_BASE_URL = "http://build.chromium.org/buildbot/layout_test_results/" - -LAYOUT_TESTS_DIRECTORY = "LayoutTests" + os.sep - -TestExpectationsFile = test_expectations.TestExpectationsFile - - -class TestInput: - """Groups information about a test for easy passing of data.""" - - def __init__(self, filename, timeout): - """Holds the input parameters for a test. - Args: - filename: Full path to the test. - timeout: Timeout in msecs the driver should use while running the test - """ - # FIXME: filename should really be test_name as a relative path. - self.filename = filename - self.timeout = timeout - # The image_hash is used to avoid doing an image dump if the - # checksums match. The image_hash is set later, and only if it is needed - # for the test. - self.image_hash = None - - -class ResultSummary(object): - """A class for partitioning the test results we get into buckets. - - This class is basically a glorified struct and it's private to this file - so we don't bother with any information hiding.""" - - def __init__(self, expectations, test_files): - self.total = len(test_files) - self.remaining = self.total - self.expectations = expectations - self.expected = 0 - self.unexpected = 0 - self.tests_by_expectation = {} - self.tests_by_timeline = {} - self.results = {} - self.unexpected_results = {} - self.failures = {} - self.tests_by_expectation[test_expectations.SKIP] = set() - for expectation in TestExpectationsFile.EXPECTATIONS.values(): - self.tests_by_expectation[expectation] = set() - for timeline in TestExpectationsFile.TIMELINES.values(): - self.tests_by_timeline[timeline] = ( - expectations.get_tests_with_timeline(timeline)) - - def add(self, result, expected): - """Add a TestResult into the appropriate bin. - - Args: - result: TestResult from dump_render_tree_thread. - expected: whether the result was what we expected it to be. - """ - - self.tests_by_expectation[result.type].add(result.filename) - self.results[result.filename] = result - self.remaining -= 1 - if len(result.failures): - self.failures[result.filename] = result.failures - if expected: - self.expected += 1 - else: - self.unexpected_results[result.filename] = result.type - self.unexpected += 1 - - -def summarize_unexpected_results(port_obj, expectations, result_summary, - retry_summary): - """Summarize any unexpected results as a dict. - - FIXME: split this data structure into a separate class? - - Args: - port_obj: interface to port-specific hooks - expectations: test_expectations.TestExpectations object - result_summary: summary object from initial test runs - retry_summary: summary object from final test run of retried tests - Returns: - A dictionary containing a summary of the unexpected results from the - run, with the following fields: - 'version': a version indicator (1 in this version) - 'fixable': # of fixable tests (NOW - PASS) - 'skipped': # of skipped tests (NOW & SKIPPED) - 'num_regressions': # of non-flaky failures - 'num_flaky': # of flaky failures - 'num_passes': # of unexpected passes - 'tests': a dict of tests -> {'expected': '...', 'actual': '...'} - """ - results = {} - results['version'] = 1 - - tbe = result_summary.tests_by_expectation - tbt = result_summary.tests_by_timeline - results['fixable'] = len(tbt[test_expectations.NOW] - - tbe[test_expectations.PASS]) - results['skipped'] = len(tbt[test_expectations.NOW] & - tbe[test_expectations.SKIP]) - - num_passes = 0 - num_flaky = 0 - num_regressions = 0 - keywords = {} - for k, v in TestExpectationsFile.EXPECTATIONS.iteritems(): - keywords[v] = k.upper() - - tests = {} - for filename, result in result_summary.unexpected_results.iteritems(): - # Note that if a test crashed in the original run, we ignore - # whether or not it crashed when we retried it (if we retried it), - # and always consider the result not flaky. - test = port_obj.relative_test_filename(filename) - expected = expectations.get_expectations_string(filename) - actual = [keywords[result]] - - if result == test_expectations.PASS: - num_passes += 1 - elif result == test_expectations.CRASH: - num_regressions += 1 - else: - if filename not in retry_summary.unexpected_results: - actual.extend(expectations.get_expectations_string( - filename).split(" ")) - num_flaky += 1 - else: - retry_result = retry_summary.unexpected_results[filename] - if result != retry_result: - actual.append(keywords[retry_result]) - num_flaky += 1 - else: - num_regressions += 1 - - tests[test] = {} - tests[test]['expected'] = expected - tests[test]['actual'] = " ".join(actual) - - results['tests'] = tests - results['num_passes'] = num_passes - results['num_flaky'] = num_flaky - results['num_regressions'] = num_regressions - - return results - - -class TestRunner: - """A class for managing running a series of tests on a series of layout - test files.""" - - HTTP_SUBDIR = os.sep.join(['', 'http', '']) - WEBSOCKET_SUBDIR = os.sep.join(['', 'websocket', '']) - - # The per-test timeout in milliseconds, if no --time-out-ms option was - # given to run_webkit_tests. This should correspond to the default timeout - # in DumpRenderTree. - DEFAULT_TEST_TIMEOUT_MS = 6 * 1000 - - def __init__(self, port, options, printer, message_broker): - """Initialize test runner data structures. - - Args: - port: an object implementing port-specific - options: a dictionary of command line options - printer: a Printer object to record updates to. - message_broker: object used to communicate with workers. - """ - self._port = port - self._options = options - self._printer = printer - self._message_broker = message_broker - - # disable wss server. need to install pyOpenSSL on buildbots. - # self._websocket_secure_server = websocket_server.PyWebSocket( - # options.results_directory, use_tls=True, port=9323) - - # a set of test files, and the same tests as a list - self._test_files = set() - self._test_files_list = None - self._result_queue = Queue.Queue() - self._retrying = False - - def collect_tests(self, args, last_unexpected_results): - """Find all the files to test. - - Args: - args: list of test arguments from the command line - last_unexpected_results: list of unexpected results to retest, if any - - """ - paths = [self._strip_test_dir_prefix(arg) for arg in args if arg and arg != ''] - paths += last_unexpected_results - if self._options.test_list: - paths += read_test_files(self._options.test_list) - self._test_files = self._port.tests(paths) - - def _strip_test_dir_prefix(self, path): - if path.startswith(LAYOUT_TESTS_DIRECTORY): - return path[len(LAYOUT_TESTS_DIRECTORY):] - return path - - def lint(self): - # Creating the expecations for each platform/configuration pair does - # all the test list parsing and ensures it's correct syntax (e.g. no - # dupes). - for platform_name in self._port.test_platform_names(): - self.parse_expectations(platform_name, is_debug_mode=True) - self.parse_expectations(platform_name, is_debug_mode=False) - self._printer.write("") - _log.info("If there are no fail messages, errors or exceptions, " - "then the lint succeeded.") - return 0 - - def parse_expectations(self, test_platform_name, is_debug_mode): - """Parse the expectations from the test_list files and return a data - structure holding them. Throws an error if the test_list files have - invalid syntax.""" - if self._options.lint_test_files: - test_files = None - else: - test_files = self._test_files - - try: - expectations_str = self._port.test_expectations() - overrides_str = self._port.test_expectations_overrides() - self._expectations = test_expectations.TestExpectations( - self._port, test_files, expectations_str, test_platform_name, - is_debug_mode, self._options.lint_test_files, - overrides=overrides_str) - return self._expectations - except SyntaxError, err: - if self._options.lint_test_files: - print str(err) - else: - raise err - - def prepare_lists_and_print_output(self): - """Create appropriate subsets of test lists and returns a - ResultSummary object. Also prints expected test counts. - """ - - # Remove skipped - both fixable and ignored - files from the - # top-level list of files to test. - num_all_test_files = len(self._test_files) - self._printer.print_expected("Found: %d tests" % - (len(self._test_files))) - if not num_all_test_files: - _log.critical('No tests to run.') - return None - - skipped = set() - if num_all_test_files > 1 and not self._options.force: - skipped = self._expectations.get_tests_with_result_type( - test_expectations.SKIP) - self._test_files -= skipped - - # Create a sorted list of test files so the subset chunk, - # if used, contains alphabetically consecutive tests. - self._test_files_list = list(self._test_files) - if self._options.randomize_order: - random.shuffle(self._test_files_list) - else: - self._test_files_list.sort() - - # If the user specifies they just want to run a subset of the tests, - # just grab a subset of the non-skipped tests. - if self._options.run_chunk or self._options.run_part: - chunk_value = self._options.run_chunk or self._options.run_part - test_files = self._test_files_list - try: - (chunk_num, chunk_len) = chunk_value.split(":") - chunk_num = int(chunk_num) - assert(chunk_num >= 0) - test_size = int(chunk_len) - assert(test_size > 0) - except: - _log.critical("invalid chunk '%s'" % chunk_value) - return None - - # Get the number of tests - num_tests = len(test_files) - - # Get the start offset of the slice. - if self._options.run_chunk: - chunk_len = test_size - # In this case chunk_num can be really large. We need - # to make the slave fit in the current number of tests. - slice_start = (chunk_num * chunk_len) % num_tests - else: - # Validate the data. - assert(test_size <= num_tests) - assert(chunk_num <= test_size) - - # To count the chunk_len, and make sure we don't skip - # some tests, we round to the next value that fits exactly - # all the parts. - rounded_tests = num_tests - if rounded_tests % test_size != 0: - rounded_tests = (num_tests + test_size - - (num_tests % test_size)) - - chunk_len = rounded_tests / test_size - slice_start = chunk_len * (chunk_num - 1) - # It does not mind if we go over test_size. - - # Get the end offset of the slice. - slice_end = min(num_tests, slice_start + chunk_len) - - files = test_files[slice_start:slice_end] - - tests_run_msg = 'Running: %d tests (chunk slice [%d:%d] of %d)' % ( - (slice_end - slice_start), slice_start, slice_end, num_tests) - self._printer.print_expected(tests_run_msg) - - # If we reached the end and we don't have enough tests, we run some - # from the beginning. - if slice_end - slice_start < chunk_len: - extra = chunk_len - (slice_end - slice_start) - extra_msg = (' last chunk is partial, appending [0:%d]' % - extra) - self._printer.print_expected(extra_msg) - tests_run_msg += "\n" + extra_msg - files.extend(test_files[0:extra]) - tests_run_filename = os.path.join(self._options.results_directory, - "tests_run.txt") - with codecs.open(tests_run_filename, "w", "utf-8") as file: - file.write(tests_run_msg + "\n") - - len_skip_chunk = int(len(files) * len(skipped) / - float(len(self._test_files))) - skip_chunk_list = list(skipped)[0:len_skip_chunk] - skip_chunk = set(skip_chunk_list) - - # Update expectations so that the stats are calculated correctly. - # We need to pass a list that includes the right # of skipped files - # to ParseExpectations so that ResultSummary() will get the correct - # stats. So, we add in the subset of skipped files, and then - # subtract them back out. - self._test_files_list = files + skip_chunk_list - self._test_files = set(self._test_files_list) - - self._expectations = self.parse_expectations( - self._port.test_platform_name(), - self._options.configuration == 'Debug') - - self._test_files = set(files) - self._test_files_list = files - else: - skip_chunk = skipped - - result_summary = ResultSummary(self._expectations, - self._test_files | skip_chunk) - self._print_expected_results_of_type(result_summary, - test_expectations.PASS, "passes") - self._print_expected_results_of_type(result_summary, - test_expectations.FAIL, "failures") - self._print_expected_results_of_type(result_summary, - test_expectations.FLAKY, "flaky") - self._print_expected_results_of_type(result_summary, - test_expectations.SKIP, "skipped") - - if self._options.force: - self._printer.print_expected('Running all tests, including ' - 'skips (--force)') - else: - # Note that we don't actually run the skipped tests (they were - # subtracted out of self._test_files, above), but we stub out the - # results here so the statistics can remain accurate. - for test in skip_chunk: - result = test_results.TestResult(test, - failures=[], test_run_time=0, total_time_for_all_diffs=0, - time_for_diffs=0) - result.type = test_expectations.SKIP - result_summary.add(result, expected=True) - self._printer.print_expected('') - - return result_summary - - def _get_dir_for_test_file(self, test_file): - """Returns the highest-level directory by which to shard the given - test file.""" - index = test_file.rfind(os.sep + LAYOUT_TESTS_DIRECTORY) - - test_file = test_file[index + len(LAYOUT_TESTS_DIRECTORY):] - test_file_parts = test_file.split(os.sep, 1) - directory = test_file_parts[0] - test_file = test_file_parts[1] - - # The http tests are very stable on mac/linux. - # TODO(ojan): Make the http server on Windows be apache so we can - # turn shard the http tests there as well. Switching to apache is - # what made them stable on linux/mac. - return_value = directory - while ((directory != 'http' or sys.platform in ('darwin', 'linux2')) - and test_file.find(os.sep) >= 0): - test_file_parts = test_file.split(os.sep, 1) - directory = test_file_parts[0] - return_value = os.path.join(return_value, directory) - test_file = test_file_parts[1] - - return return_value - - def _get_test_input_for_file(self, test_file): - """Returns the appropriate TestInput object for the file. Mostly this - is used for looking up the timeout value (in ms) to use for the given - test.""" - if self._test_is_slow(test_file): - return TestInput(test_file, self._options.slow_time_out_ms) - return TestInput(test_file, self._options.time_out_ms) - - def _test_requires_lock(self, test_file): - """Return True if the test needs to be locked when - running multiple copies of NRWTs.""" - split_path = test_file.split(os.sep) - return 'http' in split_path or 'websocket' in split_path - - def _test_is_slow(self, test_file): - return self._expectations.has_modifier(test_file, - test_expectations.SLOW) - - def _shard_tests(self, test_files, use_real_shards): - """Groups tests into batches. - This helps ensure that tests that depend on each other (aka bad tests!) - continue to run together as most cross-tests dependencies tend to - occur within the same directory. If use_real_shards is false, we - put each (non-HTTP/websocket) test into its own shard for maximum - concurrency instead of trying to do any sort of real sharding. - - Return: - A list of lists of TestInput objects. - """ - # FIXME: when we added http locking, we changed how this works such - # that we always lump all of the HTTP threads into a single shard. - # That will slow down experimental-fully-parallel, but it's unclear - # what the best alternative is completely revamping how we track - # when to grab the lock. - - test_lists = [] - tests_to_http_lock = [] - if not use_real_shards: - for test_file in test_files: - test_input = self._get_test_input_for_file(test_file) - if self._test_requires_lock(test_file): - tests_to_http_lock.append(test_input) - else: - test_lists.append((".", [test_input])) - else: - tests_by_dir = {} - for test_file in test_files: - directory = self._get_dir_for_test_file(test_file) - test_input = self._get_test_input_for_file(test_file) - if self._test_requires_lock(test_file): - tests_to_http_lock.append(test_input) - else: - tests_by_dir.setdefault(directory, []) - tests_by_dir[directory].append(test_input) - # Sort by the number of tests in the dir so that the ones with the - # most tests get run first in order to maximize parallelization. - # Number of tests is a good enough, but not perfect, approximation - # of how long that set of tests will take to run. We can't just use - # a PriorityQueue until we move to Python 2.6. - for directory in tests_by_dir: - test_list = tests_by_dir[directory] - # Keep the tests in alphabetical order. - # FIXME: Remove once tests are fixed so they can be run in any - # order. - test_list.reverse() - test_list_tuple = (directory, test_list) - test_lists.append(test_list_tuple) - test_lists.sort(lambda a, b: cmp(len(b[1]), len(a[1]))) - - # Put the http tests first. There are only a couple hundred of them, - # but each http test takes a very long time to run, so sorting by the - # number of tests doesn't accurately capture how long they take to run. - if tests_to_http_lock: - tests_to_http_lock.reverse() - test_lists.insert(0, ("tests_to_http_lock", tests_to_http_lock)) - - return test_lists - - def _contains_tests(self, subdir): - for test_file in self._test_files: - if test_file.find(subdir) >= 0: - return True - return False - - def _num_workers(self): - return int(self._options.child_processes) - - def _run_tests(self, file_list, result_summary): - """Runs the tests in the file_list. - - Return: A tuple (keyboard_interrupted, thread_timings, test_timings, - individual_test_timings) - keyboard_interrupted is whether someone typed Ctrl^C - thread_timings is a list of dicts with the total runtime - of each thread with 'name', 'num_tests', 'total_time' properties - test_timings is a list of timings for each sharded subdirectory - of the form [time, directory_name, num_tests] - individual_test_timings is a list of run times for each test - in the form {filename:filename, test_run_time:test_run_time} - result_summary: summary object to populate with the results - """ - - self._printer.print_update('Sharding tests ...') - num_workers = self._num_workers() - test_lists = self._shard_tests(file_list, - num_workers > 1 and not self._options.experimental_fully_parallel) - filename_queue = Queue.Queue() - for item in test_lists: - filename_queue.put(item) - - self._printer.print_update('Starting %s ...' % - grammar.pluralize('worker', num_workers)) - message_broker = self._message_broker - self._current_filename_queue = filename_queue - self._current_result_summary = result_summary - threads = message_broker.start_workers(self) - - self._printer.print_update("Starting testing ...") - keyboard_interrupted = False - try: - message_broker.run_message_loop() - except KeyboardInterrupt: - _log.info("Interrupted, exiting") - message_broker.cancel_workers() - keyboard_interrupted = True - except: - # Unexpected exception; don't try to clean up workers. - _log.info("Exception raised, exiting") - raise - - thread_timings, test_timings, individual_test_timings = \ - self._collect_timing_info(threads) - - return (keyboard_interrupted, thread_timings, test_timings, - individual_test_timings) - - def update(self): - self.update_summary(self._current_result_summary) - - def _collect_timing_info(self, threads): - test_timings = {} - individual_test_timings = [] - thread_timings = [] - - for thread in threads: - thread_timings.append({'name': thread.getName(), - 'num_tests': thread.get_num_tests(), - 'total_time': thread.get_total_time()}) - test_timings.update(thread.get_test_group_timing_stats()) - individual_test_timings.extend(thread.get_test_results()) - - return (thread_timings, test_timings, individual_test_timings) - - def needs_http(self): - """Returns whether the test runner needs an HTTP server.""" - return self._contains_tests(self.HTTP_SUBDIR) - - def needs_websocket(self): - """Returns whether the test runner needs a WEBSOCKET server.""" - return self._contains_tests(self.WEBSOCKET_SUBDIR) - - def set_up_run(self): - """Configures the system to be ready to run tests. - - Returns a ResultSummary object if we should continue to run tests, - or None if we should abort. - - """ - # This must be started before we check the system dependencies, - # since the helper may do things to make the setup correct. - self._printer.print_update("Starting helper ...") - self._port.start_helper() - - # Check that the system dependencies (themes, fonts, ...) are correct. - if not self._options.nocheck_sys_deps: - self._printer.print_update("Checking system dependencies ...") - if not self._port.check_sys_deps(self.needs_http()): - self._port.stop_helper() - return None - - if self._options.clobber_old_results: - self._clobber_old_results() - - # Create the output directory if it doesn't already exist. - self._port.maybe_make_directory(self._options.results_directory) - - self._port.setup_test_run() - - self._printer.print_update("Preparing tests ...") - result_summary = self.prepare_lists_and_print_output() - if not result_summary: - return None - - return result_summary - - def run(self, result_summary): - """Run all our tests on all our test files. - - For each test file, we run each test type. If there are any failures, - we collect them for reporting. - - Args: - result_summary: a summary object tracking the test results. - - Return: - The number of unexpected results (0 == success) - """ - # gather_test_files() must have been called first to initialize us. - # If we didn't find any files to test, we've errored out already in - # prepare_lists_and_print_output(). - assert(len(self._test_files)) - - start_time = time.time() - - keyboard_interrupted, thread_timings, test_timings, \ - individual_test_timings = ( - self._run_tests(self._test_files_list, result_summary)) - - # We exclude the crashes from the list of results to retry, because - # we want to treat even a potentially flaky crash as an error. - failures = self._get_failures(result_summary, include_crashes=False) - retry_summary = result_summary - while (len(failures) and self._options.retry_failures and - not self._retrying and not keyboard_interrupted): - _log.info('') - _log.info("Retrying %d unexpected failure(s) ..." % len(failures)) - _log.info('') - self._retrying = True - retry_summary = ResultSummary(self._expectations, failures.keys()) - # Note that we intentionally ignore the return value here. - self._run_tests(failures.keys(), retry_summary) - failures = self._get_failures(retry_summary, include_crashes=True) - - end_time = time.time() - - self._print_timing_statistics(end_time - start_time, - thread_timings, test_timings, - individual_test_timings, - result_summary) - - self._print_result_summary(result_summary) - - sys.stdout.flush() - sys.stderr.flush() - - self._printer.print_one_line_summary(result_summary.total, - result_summary.expected, - result_summary.unexpected) - - unexpected_results = summarize_unexpected_results(self._port, - self._expectations, result_summary, retry_summary) - self._printer.print_unexpected_results(unexpected_results) - - if self._options.record_results: - # Write the same data to log files and upload generated JSON files - # to appengine server. - self._upload_json_files(unexpected_results, result_summary, - individual_test_timings) - - # Write the summary to disk (results.html) and display it if requested. - wrote_results = self._write_results_html_file(result_summary) - if self._options.show_results and wrote_results: - self._show_results_html_file() - - # Now that we've completed all the processing we can, we re-raise - # a KeyboardInterrupt if necessary so the caller can handle it. - if keyboard_interrupted: - raise KeyboardInterrupt - - # Ignore flaky failures and unexpected passes so we don't turn the - # bot red for those. - return unexpected_results['num_regressions'] - - def clean_up_run(self): - """Restores the system after we're done running tests.""" - - _log.debug("flushing stdout") - sys.stdout.flush() - _log.debug("flushing stderr") - sys.stderr.flush() - _log.debug("stopping helper") - self._port.stop_helper() - - def update_summary(self, result_summary): - """Update the summary and print results with any completed tests.""" - while True: - try: - result = test_results.TestResult.loads(self._result_queue.get_nowait()) - except Queue.Empty: - return - - expected = self._expectations.matches_an_expected_result( - result.filename, result.type, self._options.pixel_tests) - result_summary.add(result, expected) - exp_str = self._expectations.get_expectations_string( - result.filename) - got_str = self._expectations.expectation_to_string(result.type) - self._printer.print_test_result(result, expected, exp_str, got_str) - self._printer.print_progress(result_summary, self._retrying, - self._test_files_list) - - def _clobber_old_results(self): - # Just clobber the actual test results directories since the other - # files in the results directory are explicitly used for cross-run - # tracking. - self._printer.print_update("Clobbering old results in %s" % - self._options.results_directory) - layout_tests_dir = self._port.layout_tests_dir() - possible_dirs = self._port.test_dirs() - for dirname in possible_dirs: - if os.path.isdir(os.path.join(layout_tests_dir, dirname)): - shutil.rmtree(os.path.join(self._options.results_directory, - dirname), - ignore_errors=True) - - def _get_failures(self, result_summary, include_crashes): - """Filters a dict of results and returns only the failures. - - Args: - result_summary: the results of the test run - include_crashes: whether crashes are included in the output. - We use False when finding the list of failures to retry - to see if the results were flaky. Although the crashes may also be - flaky, we treat them as if they aren't so that they're not ignored. - Returns: - a dict of files -> results - """ - failed_results = {} - for test, result in result_summary.unexpected_results.iteritems(): - if (result == test_expectations.PASS or - result == test_expectations.CRASH and not include_crashes): - continue - failed_results[test] = result - - return failed_results - - def _upload_json_files(self, unexpected_results, result_summary, - individual_test_timings): - """Writes the results of the test run as JSON files into the results - dir and upload the files to the appengine server. - - There are three different files written into the results dir: - unexpected_results.json: A short list of any unexpected results. - This is used by the buildbots to display results. - expectations.json: This is used by the flakiness dashboard. - results.json: A full list of the results - used by the flakiness - dashboard and the aggregate results dashboard. - - Args: - unexpected_results: dict of unexpected results - result_summary: full summary object - individual_test_timings: list of test times (used by the flakiness - dashboard). - """ - results_directory = self._options.results_directory - _log.debug("Writing JSON files in %s." % results_directory) - unexpected_json_path = os.path.join(results_directory, "unexpected_results.json") - with codecs.open(unexpected_json_path, "w", "utf-8") as file: - simplejson.dump(unexpected_results, file, sort_keys=True, indent=2) - - # Write a json file of the test_expectations.txt file for the layout - # tests dashboard. - expectations_path = os.path.join(results_directory, "expectations.json") - expectations_json = \ - self._expectations.get_expectations_json_for_all_platforms() - with codecs.open(expectations_path, "w", "utf-8") as file: - file.write(u"ADD_EXPECTATIONS(%s);" % expectations_json) - - generator = json_layout_results_generator.JSONLayoutResultsGenerator( - self._port, self._options.builder_name, self._options.build_name, - self._options.build_number, self._options.results_directory, - BUILDER_BASE_URL, individual_test_timings, - self._expectations, result_summary, self._test_files_list, - not self._options.upload_full_results, - self._options.test_results_server, - "layout-tests", - self._options.master_name) - - _log.debug("Finished writing JSON files.") - - json_files = ["expectations.json"] - if self._options.upload_full_results: - json_files.append("results.json") - else: - json_files.append("incremental_results.json") - - generator.upload_json_files(json_files) - - def _print_config(self): - """Prints the configuration for the test run.""" - p = self._printer - p.print_config("Using port '%s'" % self._port.name()) - p.print_config("Placing test results in %s" % - self._options.results_directory) - if self._options.new_baseline: - p.print_config("Placing new baselines in %s" % - self._port.baseline_path()) - p.print_config("Using %s build" % self._options.configuration) - if self._options.pixel_tests: - p.print_config("Pixel tests enabled") - else: - p.print_config("Pixel tests disabled") - - p.print_config("Regular timeout: %s, slow test timeout: %s" % - (self._options.time_out_ms, - self._options.slow_time_out_ms)) - - if self._num_workers() == 1: - p.print_config("Running one %s" % self._port.driver_name()) - else: - p.print_config("Running %s %ss in parallel" % - (self._options.child_processes, - self._port.driver_name())) - p.print_config('Command line: ' + - ' '.join(self._port.driver_cmd_line())) - p.print_config("Worker model: %s" % self._options.worker_model) - p.print_config("") - - def _print_expected_results_of_type(self, result_summary, - result_type, result_type_str): - """Print the number of the tests in a given result class. - - Args: - result_summary - the object containing all the results to report on - result_type - the particular result type to report in the summary. - result_type_str - a string description of the result_type. - """ - tests = self._expectations.get_tests_with_result_type(result_type) - now = result_summary.tests_by_timeline[test_expectations.NOW] - wontfix = result_summary.tests_by_timeline[test_expectations.WONTFIX] - - # We use a fancy format string in order to print the data out in a - # nicely-aligned table. - fmtstr = ("Expect: %%5d %%-8s (%%%dd now, %%%dd wontfix)" - % (self._num_digits(now), self._num_digits(wontfix))) - self._printer.print_expected(fmtstr % - (len(tests), result_type_str, len(tests & now), len(tests & wontfix))) - - def _num_digits(self, num): - """Returns the number of digits needed to represent the length of a - sequence.""" - ndigits = 1 - if len(num): - ndigits = int(math.log10(len(num))) + 1 - return ndigits - - def _print_timing_statistics(self, total_time, thread_timings, - directory_test_timings, individual_test_timings, - result_summary): - """Record timing-specific information for the test run. - - Args: - total_time: total elapsed time (in seconds) for the test run - thread_timings: wall clock time each thread ran for - directory_test_timings: timing by directory - individual_test_timings: timing by file - result_summary: summary object for the test run - """ - self._printer.print_timing("Test timing:") - self._printer.print_timing(" %6.2f total testing time" % total_time) - self._printer.print_timing("") - self._printer.print_timing("Thread timing:") - cuml_time = 0 - for t in thread_timings: - self._printer.print_timing(" %10s: %5d tests, %6.2f secs" % - (t['name'], t['num_tests'], t['total_time'])) - cuml_time += t['total_time'] - self._printer.print_timing(" %6.2f cumulative, %6.2f optimal" % - (cuml_time, cuml_time / int(self._options.child_processes))) - self._printer.print_timing("") - - self._print_aggregate_test_statistics(individual_test_timings) - self._print_individual_test_times(individual_test_timings, - result_summary) - self._print_directory_timings(directory_test_timings) - - def _print_aggregate_test_statistics(self, individual_test_timings): - """Prints aggregate statistics (e.g. median, mean, etc.) for all tests. - Args: - individual_test_timings: List of dump_render_tree_thread.TestStats - for all tests. - """ - test_types = [] # Unit tests don't actually produce any timings. - if individual_test_timings: - test_types = individual_test_timings[0].time_for_diffs.keys() - times_for_dump_render_tree = [] - times_for_diff_processing = [] - times_per_test_type = {} - for test_type in test_types: - times_per_test_type[test_type] = [] - - for test_stats in individual_test_timings: - times_for_dump_render_tree.append(test_stats.test_run_time) - times_for_diff_processing.append( - test_stats.total_time_for_all_diffs) - time_for_diffs = test_stats.time_for_diffs - for test_type in test_types: - times_per_test_type[test_type].append( - time_for_diffs[test_type]) - - self._print_statistics_for_test_timings( - "PER TEST TIME IN TESTSHELL (seconds):", - times_for_dump_render_tree) - self._print_statistics_for_test_timings( - "PER TEST DIFF PROCESSING TIMES (seconds):", - times_for_diff_processing) - for test_type in test_types: - self._print_statistics_for_test_timings( - "PER TEST TIMES BY TEST TYPE: %s" % test_type, - times_per_test_type[test_type]) - - def _print_individual_test_times(self, individual_test_timings, - result_summary): - """Prints the run times for slow, timeout and crash tests. - Args: - individual_test_timings: List of dump_render_tree_thread.TestStats - for all tests. - result_summary: summary object for test run - """ - # Reverse-sort by the time spent in DumpRenderTree. - individual_test_timings.sort(lambda a, b: - cmp(b.test_run_time, a.test_run_time)) - - num_printed = 0 - slow_tests = [] - timeout_or_crash_tests = [] - unexpected_slow_tests = [] - for test_tuple in individual_test_timings: - filename = test_tuple.filename - is_timeout_crash_or_slow = False - if self._test_is_slow(filename): - is_timeout_crash_or_slow = True - slow_tests.append(test_tuple) - - if filename in result_summary.failures: - result = result_summary.results[filename].type - if (result == test_expectations.TIMEOUT or - result == test_expectations.CRASH): - is_timeout_crash_or_slow = True - timeout_or_crash_tests.append(test_tuple) - - if (not is_timeout_crash_or_slow and - num_printed < printing.NUM_SLOW_TESTS_TO_LOG): - num_printed = num_printed + 1 - unexpected_slow_tests.append(test_tuple) - - self._printer.print_timing("") - self._print_test_list_timing("%s slowest tests that are not " - "marked as SLOW and did not timeout/crash:" % - printing.NUM_SLOW_TESTS_TO_LOG, unexpected_slow_tests) - self._printer.print_timing("") - self._print_test_list_timing("Tests marked as SLOW:", slow_tests) - self._printer.print_timing("") - self._print_test_list_timing("Tests that timed out or crashed:", - timeout_or_crash_tests) - self._printer.print_timing("") - - def _print_test_list_timing(self, title, test_list): - """Print timing info for each test. - - Args: - title: section heading - test_list: tests that fall in this section - """ - if self._printer.disabled('slowest'): - return - - self._printer.print_timing(title) - for test_tuple in test_list: - filename = test_tuple.filename[len( - self._port.layout_tests_dir()) + 1:] - filename = filename.replace('\\', '/') - test_run_time = round(test_tuple.test_run_time, 1) - self._printer.print_timing(" %s took %s seconds" % - (filename, test_run_time)) - - def _print_directory_timings(self, directory_test_timings): - """Print timing info by directory for any directories that - take > 10 seconds to run. - - Args: - directory_test_timing: time info for each directory - """ - timings = [] - for directory in directory_test_timings: - num_tests, time_for_directory = directory_test_timings[directory] - timings.append((round(time_for_directory, 1), directory, - num_tests)) - timings.sort() - - self._printer.print_timing("Time to process slowest subdirectories:") - min_seconds_to_print = 10 - for timing in timings: - if timing[0] > min_seconds_to_print: - self._printer.print_timing( - " %s took %s seconds to run %s tests." % (timing[1], - timing[0], timing[2])) - self._printer.print_timing("") - - def _print_statistics_for_test_timings(self, title, timings): - """Prints the median, mean and standard deviation of the values in - timings. - - Args: - title: Title for these timings. - timings: A list of floats representing times. - """ - self._printer.print_timing(title) - timings.sort() - - num_tests = len(timings) - if not num_tests: - return - percentile90 = timings[int(.9 * num_tests)] - percentile99 = timings[int(.99 * num_tests)] - - if num_tests % 2 == 1: - median = timings[((num_tests - 1) / 2) - 1] - else: - lower = timings[num_tests / 2 - 1] - upper = timings[num_tests / 2] - median = (float(lower + upper)) / 2 - - mean = sum(timings) / num_tests - - for time in timings: - sum_of_deviations = math.pow(time - mean, 2) - - std_deviation = math.sqrt(sum_of_deviations / num_tests) - self._printer.print_timing(" Median: %6.3f" % median) - self._printer.print_timing(" Mean: %6.3f" % mean) - self._printer.print_timing(" 90th percentile: %6.3f" % percentile90) - self._printer.print_timing(" 99th percentile: %6.3f" % percentile99) - self._printer.print_timing(" Standard dev: %6.3f" % std_deviation) - self._printer.print_timing("") - - def _print_result_summary(self, result_summary): - """Print a short summary about how many tests passed. - - Args: - result_summary: information to log - """ - failed = len(result_summary.failures) - skipped = len( - result_summary.tests_by_expectation[test_expectations.SKIP]) - total = result_summary.total - passed = total - failed - skipped - pct_passed = 0.0 - if total > 0: - pct_passed = float(passed) * 100 / total - - self._printer.print_actual("") - self._printer.print_actual("=> Results: %d/%d tests passed (%.1f%%)" % - (passed, total, pct_passed)) - self._printer.print_actual("") - self._print_result_summary_entry(result_summary, - test_expectations.NOW, "Tests to be fixed") - - self._printer.print_actual("") - self._print_result_summary_entry(result_summary, - test_expectations.WONTFIX, - "Tests that will only be fixed if they crash (WONTFIX)") - self._printer.print_actual("") - - def _print_result_summary_entry(self, result_summary, timeline, - heading): - """Print a summary block of results for a particular timeline of test. - - Args: - result_summary: summary to print results for - timeline: the timeline to print results for (NOT, WONTFIX, etc.) - heading: a textual description of the timeline - """ - total = len(result_summary.tests_by_timeline[timeline]) - not_passing = (total - - len(result_summary.tests_by_expectation[test_expectations.PASS] & - result_summary.tests_by_timeline[timeline])) - self._printer.print_actual("=> %s (%d):" % (heading, not_passing)) - - for result in TestExpectationsFile.EXPECTATION_ORDER: - if result == test_expectations.PASS: - continue - results = (result_summary.tests_by_expectation[result] & - result_summary.tests_by_timeline[timeline]) - desc = TestExpectationsFile.EXPECTATION_DESCRIPTIONS[result] - if not_passing and len(results): - pct = len(results) * 100.0 / not_passing - self._printer.print_actual(" %5d %-24s (%4.1f%%)" % - (len(results), desc[len(results) != 1], pct)) - - def _results_html(self, test_files, failures, title="Test Failures", override_time=None): - """ - test_files = a list of file paths - failures = dictionary mapping test paths to failure objects - title = title printed at top of test - override_time = current time (used by unit tests) - """ - page = """<html> - <head> - <title>Layout Test Results (%(time)s)</title> - </head> - <body> - <h2>%(title)s (%(time)s)</h2> - """ % {'title': title, 'time': override_time or time.asctime()} - - for test_file in sorted(test_files): - test_name = self._port.relative_test_filename(test_file) - test_url = self._port.filename_to_uri(test_file) - page += u"<p><a href='%s'>%s</a><br />\n" % (test_url, test_name) - test_failures = failures.get(test_file, []) - for failure in test_failures: - page += (u" %s<br/>" % - failure.result_html_output(test_name)) - page += "</p>\n" - page += "</body></html>\n" - return page - - def _write_results_html_file(self, result_summary): - """Write results.html which is a summary of tests that failed. - - Args: - result_summary: a summary of the results :) - - Returns: - True if any results were written (since expected failures may be - omitted) - """ - # test failures - if self._options.full_results_html: - results_title = "Test Failures" - test_files = result_summary.failures.keys() - else: - results_title = "Unexpected Test Failures" - unexpected_failures = self._get_failures(result_summary, - include_crashes=True) - test_files = unexpected_failures.keys() - if not len(test_files): - return False - - out_filename = os.path.join(self._options.results_directory, - "results.html") - with codecs.open(out_filename, "w", "utf-8") as results_file: - html = self._results_html(test_files, result_summary.failures, results_title) - results_file.write(html) - - return True - - def _show_results_html_file(self): - """Shows the results.html page.""" - results_filename = os.path.join(self._options.results_directory, - "results.html") - self._port.show_results_html_file(results_filename) - - -def read_test_files(files): - tests = [] - for file in files: - try: - with codecs.open(file, 'r', 'utf-8') as file_contents: - # FIXME: This could be cleaner using a list comprehension. - for line in file_contents: - line = test_expectations.strip_comments(line) - if line: - tests.append(line) - except IOError, e: - if e.errno == errno.ENOENT: - _log.critical('') - _log.critical('--test-list file "%s" not found' % file) - raise - return tests - - -def run(port, options, args, regular_output=sys.stderr, - buildbot_output=sys.stdout): - """Run the tests. - - Args: - port: Port object for port-specific behavior - options: a dictionary of command line options - args: a list of sub directories or files to test - regular_output: a stream-like object that we can send logging/debug - output to - buildbot_output: a stream-like object that we can write all output that - is intended to be parsed by the buildbot to - Returns: - the number of unexpected results that occurred, or -1 if there is an - error. - - """ - _set_up_derived_options(port, options) - - printer = printing.Printer(port, options, regular_output, buildbot_output, - int(options.child_processes), options.experimental_fully_parallel) - if options.help_printing: - printer.help_printing() - printer.cleanup() - return 0 - - last_unexpected_results = _gather_unexpected_results(options) - if options.print_last_failures: - printer.write("\n".join(last_unexpected_results) + "\n") - printer.cleanup() - return 0 - - broker = message_broker.get(port, options) - - # We wrap any parts of the run that are slow or likely to raise exceptions - # in a try/finally to ensure that we clean up the logging configuration. - num_unexpected_results = -1 - try: - test_runner = TestRunner(port, options, printer, broker) - test_runner._print_config() - - printer.print_update("Collecting tests ...") - try: - test_runner.collect_tests(args, last_unexpected_results) - except IOError, e: - if e.errno == errno.ENOENT: - return -1 - raise - - printer.print_update("Parsing expectations ...") - if options.lint_test_files: - return test_runner.lint() - test_runner.parse_expectations(port.test_platform_name(), - options.configuration == 'Debug') - - printer.print_update("Checking build ...") - if not port.check_build(test_runner.needs_http()): - _log.error("Build check failed") - return -1 - - result_summary = test_runner.set_up_run() - if result_summary: - num_unexpected_results = test_runner.run(result_summary) - test_runner.clean_up_run() - _log.debug("Testing completed, Exit status: %d" % - num_unexpected_results) - finally: - broker.cleanup() - printer.cleanup() - - return num_unexpected_results - - -def _set_up_derived_options(port_obj, options): - """Sets the options values that depend on other options values.""" - - if options.worker_model == 'inline': - if options.child_processes and int(options.child_processes) > 1: - _log.warning("--worker-model=inline overrides --child-processes") - options.child_processes = "1" - if not options.child_processes: - options.child_processes = os.environ.get("WEBKIT_TEST_CHILD_PROCESSES", - str(port_obj.default_child_processes())) - - if not options.configuration: - options.configuration = port_obj.default_configuration() - - if options.pixel_tests is None: - options.pixel_tests = True - - if not options.use_apache: - options.use_apache = sys.platform in ('darwin', 'linux2') - - if not os.path.isabs(options.results_directory): - # This normalizes the path to the build dir. - # FIXME: how this happens is not at all obvious; this is a dumb - # interface and should be cleaned up. - options.results_directory = port_obj.results_directory() - - if not options.time_out_ms: - if options.configuration == "Debug": - options.time_out_ms = str(2 * TestRunner.DEFAULT_TEST_TIMEOUT_MS) - else: - options.time_out_ms = str(TestRunner.DEFAULT_TEST_TIMEOUT_MS) - - options.slow_time_out_ms = str(5 * int(options.time_out_ms)) - - -def _gather_unexpected_results(options): - """Returns the unexpected results from the previous run, if any.""" - last_unexpected_results = [] - if options.print_last_failures or options.retest_last_failures: - unexpected_results_filename = os.path.join( - options.results_directory, "unexpected_results.json") - with codecs.open(unexpected_results_filename, "r", "utf-8") as file: - results = simplejson.load(file) - last_unexpected_results = results['tests'].keys() - return last_unexpected_results - - -def _compat_shim_callback(option, opt_str, value, parser): - print "Ignoring unsupported option: %s" % opt_str - - -def _compat_shim_option(option_name, **kwargs): - return optparse.make_option(option_name, action="callback", - callback=_compat_shim_callback, - help="Ignored, for old-run-webkit-tests compat only.", **kwargs) - - -def parse_args(args=None): - """Provides a default set of command line args. - - Returns a tuple of options, args from optparse""" - - # FIXME: All of these options should be stored closer to the code which - # FIXME: actually uses them. configuration_options should move - # FIXME: to WebKitPort and be shared across all scripts. - configuration_options = [ - optparse.make_option("-t", "--target", dest="configuration", - help="(DEPRECATED)"), - # FIXME: --help should display which configuration is default. - optparse.make_option('--debug', action='store_const', const='Debug', - dest="configuration", - help='Set the configuration to Debug'), - optparse.make_option('--release', action='store_const', - const='Release', dest="configuration", - help='Set the configuration to Release'), - # old-run-webkit-tests also accepts -c, --configuration CONFIGURATION. - ] - - print_options = printing.print_options() - - # FIXME: These options should move onto the ChromiumPort. - chromium_options = [ - optparse.make_option("--chromium", action="store_true", default=False, - help="use the Chromium port"), - optparse.make_option("--startup-dialog", action="store_true", - default=False, help="create a dialog on DumpRenderTree startup"), - optparse.make_option("--gp-fault-error-box", action="store_true", - default=False, help="enable Windows GP fault error box"), - optparse.make_option("--multiple-loads", - type="int", help="turn on multiple loads of each test"), - optparse.make_option("--js-flags", - type="string", help="JavaScript flags to pass to tests"), - optparse.make_option("--nocheck-sys-deps", action="store_true", - default=False, - help="Don't check the system dependencies (themes)"), - optparse.make_option("--use-drt", action="store_true", - default=None, - help="Use DumpRenderTree instead of test_shell"), - optparse.make_option("--accelerated-compositing", - action="store_true", - help="Use hardware-accelated compositing for rendering"), - optparse.make_option("--no-accelerated-compositing", - action="store_false", - dest="accelerated_compositing", - help="Don't use hardware-accelerated compositing for rendering"), - optparse.make_option("--accelerated-2d-canvas", - action="store_true", - help="Use hardware-accelerated 2D Canvas calls"), - optparse.make_option("--no-accelerated-2d-canvas", - action="store_false", - dest="accelerated_2d_canvas", - help="Don't use hardware-accelerated 2D Canvas calls"), - ] - - # Missing Mac-specific old-run-webkit-tests options: - # FIXME: Need: -g, --guard for guard malloc support on Mac. - # FIXME: Need: -l --leaks Enable leaks checking. - # FIXME: Need: --sample-on-timeout Run sample on timeout - - old_run_webkit_tests_compat = [ - # NRWT doesn't generate results by default anyway. - _compat_shim_option("--no-new-test-results"), - # NRWT doesn't sample on timeout yet anyway. - _compat_shim_option("--no-sample-on-timeout"), - # FIXME: NRWT needs to support remote links eventually. - _compat_shim_option("--use-remote-links-to-tests"), - # FIXME: NRWT doesn't need this option as much since failures are - # designed to be cheap. We eventually plan to add this support. - _compat_shim_option("--exit-after-n-failures", nargs=1, type="int"), - ] - - results_options = [ - # NEED for bots: --use-remote-links-to-tests Link to test files - # within the SVN repository in the results. - optparse.make_option("-p", "--pixel-tests", action="store_true", - dest="pixel_tests", help="Enable pixel-to-pixel PNG comparisons"), - optparse.make_option("--no-pixel-tests", action="store_false", - dest="pixel_tests", help="Disable pixel-to-pixel PNG comparisons"), - optparse.make_option("--tolerance", - help="Ignore image differences less than this percentage (some " - "ports may ignore this option)", type="float"), - optparse.make_option("--results-directory", - default="layout-test-results", - help="Output results directory source dir, relative to Debug or " - "Release"), - optparse.make_option("--new-baseline", action="store_true", - default=False, help="Save all generated results as new baselines " - "into the platform directory, overwriting whatever's " - "already there."), - optparse.make_option("--reset-results", action="store_true", - default=False, help="Reset any existing baselines to the " - "generated results"), - optparse.make_option("--no-show-results", action="store_false", - default=True, dest="show_results", - help="Don't launch a browser with results after the tests " - "are done"), - # FIXME: We should have a helper function to do this sort of - # deprectated mapping and automatically log, etc. - optparse.make_option("--noshow-results", action="store_false", - dest="show_results", - help="Deprecated, same as --no-show-results."), - optparse.make_option("--no-launch-safari", action="store_false", - dest="show_results", - help="old-run-webkit-tests compat, same as --noshow-results."), - # old-run-webkit-tests: - # --[no-]launch-safari Launch (or do not launch) Safari to display - # test results (default: launch) - optparse.make_option("--full-results-html", action="store_true", - default=False, - help="Show all failures in results.html, rather than only " - "regressions"), - optparse.make_option("--clobber-old-results", action="store_true", - default=False, help="Clobbers test results from previous runs."), - optparse.make_option("--platform", - help="Override the platform for expected results"), - optparse.make_option("--no-record-results", action="store_false", - default=True, dest="record_results", - help="Don't record the results."), - # old-run-webkit-tests also has HTTP toggle options: - # --[no-]http Run (or do not run) http tests - # (default: run) - ] - - test_options = [ - optparse.make_option("--build", dest="build", - action="store_true", default=True, - help="Check to ensure the DumpRenderTree build is up-to-date " - "(default)."), - optparse.make_option("--no-build", dest="build", - action="store_false", help="Don't check to see if the " - "DumpRenderTree build is up-to-date."), - # old-run-webkit-tests has --valgrind instead of wrapper. - optparse.make_option("--wrapper", - help="wrapper command to insert before invocations of " - "DumpRenderTree; option is split on whitespace before " - "running. (Example: --wrapper='valgrind --smc-check=all')"), - # old-run-webkit-tests: - # -i|--ignore-tests Comma-separated list of directories - # or tests to ignore - optparse.make_option("--test-list", action="append", - help="read list of tests to run from file", metavar="FILE"), - # old-run-webkit-tests uses --skipped==[default|ignore|only] - # instead of --force: - optparse.make_option("--force", action="store_true", default=False, - help="Run all tests, even those marked SKIP in the test list"), - optparse.make_option("--use-apache", action="store_true", - default=False, help="Whether to use apache instead of lighttpd."), - optparse.make_option("--time-out-ms", - help="Set the timeout for each test"), - # old-run-webkit-tests calls --randomize-order --random: - optparse.make_option("--randomize-order", action="store_true", - default=False, help=("Run tests in random order (useful " - "for tracking down corruption)")), - optparse.make_option("--run-chunk", - help=("Run a specified chunk (n:l), the nth of len l, " - "of the layout tests")), - optparse.make_option("--run-part", help=("Run a specified part (n:m), " - "the nth of m parts, of the layout tests")), - # old-run-webkit-tests calls --batch-size: --nthly n - # Restart DumpRenderTree every n tests (default: 1000) - optparse.make_option("--batch-size", - help=("Run a the tests in batches (n), after every n tests, " - "DumpRenderTree is relaunched."), type="int", default=0), - # old-run-webkit-tests calls --run-singly: -1|--singly - # Isolate each test case run (implies --nthly 1 --verbose) - optparse.make_option("--run-singly", action="store_true", - default=False, help="run a separate DumpRenderTree for each test"), - optparse.make_option("--child-processes", - help="Number of DumpRenderTrees to run in parallel."), - # FIXME: Display default number of child processes that will run. - optparse.make_option("--worker-model", action="store", - default="threads", help=("controls worker model. Valid values are " - "'inline' and 'threads' (default).")), - optparse.make_option("--experimental-fully-parallel", - action="store_true", default=False, - help="run all tests in parallel"), - # FIXME: Need --exit-after-n-failures N - # Exit after the first N failures instead of running all tests - # FIXME: Need --exit-after-n-crashes N - # Exit after the first N crashes instead of running all tests - # FIXME: consider: --iterations n - # Number of times to run the set of tests (e.g. ABCABCABC) - optparse.make_option("--print-last-failures", action="store_true", - default=False, help="Print the tests in the last run that " - "had unexpected failures (or passes) and then exit."), - optparse.make_option("--retest-last-failures", action="store_true", - default=False, help="re-test the tests in the last run that " - "had unexpected failures (or passes)."), - optparse.make_option("--retry-failures", action="store_true", - default=True, - help="Re-try any tests that produce unexpected results (default)"), - optparse.make_option("--no-retry-failures", action="store_false", - dest="retry_failures", - help="Don't re-try any tests that produce unexpected results."), - ] - - misc_options = [ - optparse.make_option("--lint-test-files", action="store_true", - default=False, help=("Makes sure the test files parse for all " - "configurations. Does not run any tests.")), - ] - - # FIXME: Move these into json_results_generator.py - results_json_options = [ - optparse.make_option("--master-name", help="The name of the buildbot master."), - optparse.make_option("--builder-name", default="DUMMY_BUILDER_NAME", - help=("The name of the builder shown on the waterfall running " - "this script e.g. WebKit.")), - optparse.make_option("--build-name", default="DUMMY_BUILD_NAME", - help=("The name of the builder used in its path, e.g. " - "webkit-rel.")), - optparse.make_option("--build-number", default="DUMMY_BUILD_NUMBER", - help=("The build number of the builder running this script.")), - optparse.make_option("--test-results-server", default="", - help=("If specified, upload results json files to this appengine " - "server.")), - optparse.make_option("--upload-full-results", - action="store_true", - default=False, - help="If true, upload full json results to server."), - ] - - option_list = (configuration_options + print_options + - chromium_options + results_options + test_options + - misc_options + results_json_options + - old_run_webkit_tests_compat) - option_parser = optparse.OptionParser(option_list=option_list) - - return option_parser.parse_args(args) - - -def main(): - options, args = parse_args() - port_obj = port.get(options.platform, options) - return run(port_obj, options, args) - - -if '__main__' == __name__: - try: - sys.exit(main()) - except KeyboardInterrupt: - # this mirrors what the shell normally does - sys.exit(signal.SIGINT + 128) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py deleted file mode 100644 index 20a4ac0..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/run_webkit_tests_unittest.py +++ /dev/null @@ -1,540 +0,0 @@ -#!/usr/bin/python -# Copyright (C) 2010 Google Inc. All rights reserved. -# Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged -# -# 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 run_webkit_tests.""" - -import codecs -import itertools -import logging -import os -import Queue -import shutil -import sys -import tempfile -import thread -import time -import threading -import unittest - -from webkitpy.common import array_stream -from webkitpy.common.system import outputcapture -from webkitpy.common.system import user -from webkitpy.layout_tests import port -from webkitpy.layout_tests import run_webkit_tests -from webkitpy.layout_tests.layout_package import dump_render_tree_thread -from webkitpy.layout_tests.port.test import TestPort, TestDriver -from webkitpy.python24.versioning import compare_version -from webkitpy.test.skip import skip_if - -from webkitpy.thirdparty.mock import Mock - - -class MockUser(): - def __init__(self): - self.url = None - - def open_url(self, url): - self.url = url - - -def passing_run(extra_args=None, port_obj=None, record_results=False, - tests_included=False): - extra_args = extra_args or [] - args = ['--print', 'nothing'] - if not '--platform' in extra_args: - args.extend(['--platform', 'test']) - if not record_results: - args.append('--no-record-results') - if not '--child-processes' in extra_args: - args.extend(['--worker-model', 'inline']) - args.extend(extra_args) - if not tests_included: - # We use the glob to test that globbing works. - args.extend(['passes', - 'http/tests', - 'websocket/tests', - 'failures/expected/*']) - options, parsed_args = run_webkit_tests.parse_args(args) - if not port_obj: - port_obj = port.get(port_name=options.platform, options=options, - user=MockUser()) - res = run_webkit_tests.run(port_obj, options, parsed_args) - return res == 0 - - -def logging_run(extra_args=None, port_obj=None, tests_included=False): - extra_args = extra_args or [] - args = ['--no-record-results'] - if not '--platform' in extra_args: - args.extend(['--platform', 'test']) - if not '--child-processes' in extra_args: - args.extend(['--worker-model', 'inline']) - args.extend(extra_args) - if not tests_included: - args.extend(['passes', - 'http/tests', - 'websocket/tests', - 'failures/expected/*']) - - oc = outputcapture.OutputCapture() - try: - oc.capture_output() - options, parsed_args = run_webkit_tests.parse_args(args) - user = MockUser() - if not port_obj: - port_obj = port.get(port_name=options.platform, options=options, - user=user) - buildbot_output = array_stream.ArrayStream() - regular_output = array_stream.ArrayStream() - res = run_webkit_tests.run(port_obj, options, parsed_args, - buildbot_output=buildbot_output, - regular_output=regular_output) - finally: - oc.restore_output() - return (res, buildbot_output, regular_output, user) - - -def get_tests_run(extra_args=None, tests_included=False, flatten_batches=False): - extra_args = extra_args or [] - args = [ - '--print', 'nothing', - '--platform', 'test', - '--no-record-results', - '--worker-model', 'inline'] - args.extend(extra_args) - if not tests_included: - # Not including http tests since they get run out of order (that - # behavior has its own test, see test_get_test_file_queue) - args.extend(['passes', 'failures']) - options, parsed_args = run_webkit_tests.parse_args(args) - user = MockUser() - - test_batches = [] - - class RecordingTestDriver(TestDriver): - def __init__(self, port, worker_number): - TestDriver.__init__(self, port, worker_number) - self._current_test_batch = None - - def poll(self): - # So that we don't create a new driver for every test - return None - - def stop(self): - self._current_test_batch = None - - def run_test(self, test_input): - if self._current_test_batch is None: - self._current_test_batch = [] - test_batches.append(self._current_test_batch) - test_name = self._port.relative_test_filename(test_input.filename) - self._current_test_batch.append(test_name) - return TestDriver.run_test(self, test_input) - - class RecordingTestPort(TestPort): - def create_driver(self, worker_number): - return RecordingTestDriver(self, worker_number) - - recording_port = RecordingTestPort(options=options, user=user) - logging_run(extra_args=args, port_obj=recording_port, tests_included=True) - - if flatten_batches: - return list(itertools.chain(*test_batches)) - - return test_batches - -class MainTest(unittest.TestCase): - def test_accelerated_compositing(self): - # This just tests that we recognize the command line args - self.assertTrue(passing_run(['--accelerated-compositing'])) - self.assertTrue(passing_run(['--no-accelerated-compositing'])) - - def test_accelerated_2d_canvas(self): - # This just tests that we recognize the command line args - self.assertTrue(passing_run(['--accelerated-2d-canvas'])) - self.assertTrue(passing_run(['--no-accelerated-2d-canvas'])) - - def test_basic(self): - self.assertTrue(passing_run()) - - def test_batch_size(self): - batch_tests_run = get_tests_run(['--batch-size', '2']) - for batch in batch_tests_run: - self.assertTrue(len(batch) <= 2, '%s had too many tests' % ', '.join(batch)) - - def test_child_process_1(self): - (res, buildbot_output, regular_output, user) = logging_run( - ['--print', 'config', '--child-processes', '1']) - self.assertTrue('Running one DumpRenderTree\n' - in regular_output.get()) - - def test_child_processes_2(self): - (res, buildbot_output, regular_output, user) = logging_run( - ['--print', 'config', '--child-processes', '2']) - self.assertTrue('Running 2 DumpRenderTrees in parallel\n' - in regular_output.get()) - - def test_exception_raised(self): - self.assertRaises(ValueError, logging_run, - ['failures/expected/exception.html'], tests_included=True) - - def test_full_results_html(self): - # FIXME: verify html? - self.assertTrue(passing_run(['--full-results-html'])) - - def test_help_printing(self): - res, out, err, user = logging_run(['--help-printing']) - self.assertEqual(res, 0) - self.assertTrue(out.empty()) - self.assertFalse(err.empty()) - - def test_hung_thread(self): - res, out, err, user = logging_run(['--run-singly', '--time-out-ms=50', - 'failures/expected/hang.html'], - tests_included=True) - self.assertEqual(res, 0) - self.assertFalse(out.empty()) - self.assertFalse(err.empty()) - - def test_keyboard_interrupt(self): - # Note that this also tests running a test marked as SKIP if - # you specify it explicitly. - self.assertRaises(KeyboardInterrupt, logging_run, - ['failures/expected/keyboard.html'], tests_included=True) - - def test_last_results(self): - passing_run(['--clobber-old-results'], record_results=True) - (res, buildbot_output, regular_output, user) = logging_run( - ['--print-last-failures']) - self.assertEqual(regular_output.get(), ['\n\n']) - self.assertEqual(buildbot_output.get(), []) - - def test_lint_test_files(self): - # FIXME: add errors? - res, out, err, user = logging_run(['--lint-test-files'], - tests_included=True) - self.assertEqual(res, 0) - self.assertTrue(out.empty()) - self.assertTrue(any(['lint succeeded' in msg for msg in err.get()])) - - def test_no_tests_found(self): - res, out, err, user = logging_run(['resources'], tests_included=True) - self.assertEqual(res, -1) - self.assertTrue(out.empty()) - self.assertTrue('No tests to run.\n' in err.get()) - - def test_no_tests_found_2(self): - res, out, err, user = logging_run(['foo'], tests_included=True) - self.assertEqual(res, -1) - self.assertTrue(out.empty()) - self.assertTrue('No tests to run.\n' in err.get()) - - def test_randomize_order(self): - # FIXME: verify order was shuffled - self.assertTrue(passing_run(['--randomize-order'])) - - def test_run_chunk(self): - # Test that we actually select the right chunk - all_tests_run = get_tests_run(flatten_batches=True) - chunk_tests_run = get_tests_run(['--run-chunk', '1:4'], flatten_batches=True) - self.assertEquals(all_tests_run[4:8], chunk_tests_run) - - # Test that we wrap around if the number of tests is not evenly divisible by the chunk size - tests_to_run = ['passes/error.html', 'passes/image.html', 'passes/platform_image.html', 'passes/text.html'] - chunk_tests_run = get_tests_run(['--run-chunk', '1:3'] + tests_to_run, tests_included=True, flatten_batches=True) - self.assertEquals(['passes/text.html', 'passes/error.html', 'passes/image.html'], chunk_tests_run) - - def test_run_force(self): - # This raises an exception because we run - # failures/expected/exception.html, which is normally SKIPped. - self.assertRaises(ValueError, logging_run, ['--force']) - - def test_run_part(self): - # Test that we actually select the right part - tests_to_run = ['passes/error.html', 'passes/image.html', 'passes/platform_image.html', 'passes/text.html'] - tests_run = get_tests_run(['--run-part', '1:2'] + tests_to_run, tests_included=True, flatten_batches=True) - self.assertEquals(['passes/error.html', 'passes/image.html'], tests_run) - - # Test that we wrap around if the number of tests is not evenly divisible by the chunk size - # (here we end up with 3 parts, each with 2 tests, and we only have 4 tests total, so the - # last part repeats the first two tests). - chunk_tests_run = get_tests_run(['--run-part', '3:3'] + tests_to_run, tests_included=True, flatten_batches=True) - self.assertEquals(['passes/error.html', 'passes/image.html'], chunk_tests_run) - - def test_run_singly(self): - batch_tests_run = get_tests_run(['--run-singly']) - for batch in batch_tests_run: - self.assertEquals(len(batch), 1, '%s had too many tests' % ', '.join(batch)) - - def test_single_file(self): - tests_run = get_tests_run(['passes/text.html'], tests_included=True, flatten_batches=True) - self.assertEquals(['passes/text.html'], tests_run) - - def test_test_list(self): - filename = tempfile.mktemp() - tmpfile = file(filename, mode='w+') - tmpfile.write('passes/text.html') - tmpfile.close() - tests_run = get_tests_run(['--test-list=%s' % filename], tests_included=True, flatten_batches=True) - self.assertEquals(['passes/text.html'], tests_run) - os.remove(filename) - res, out, err, user = logging_run(['--test-list=%s' % filename], - tests_included=True) - self.assertEqual(res, -1) - self.assertFalse(err.empty()) - - def test_unexpected_failures(self): - # Run tests including the unexpected failures. - self._url_opened = None - res, out, err, user = logging_run(tests_included=True) - self.assertEqual(res, 1) - self.assertFalse(out.empty()) - self.assertFalse(err.empty()) - self.assertEqual(user.url, '/tmp/layout-test-results/results.html') - - def test_results_directory_absolute(self): - # We run a configuration that should fail, to generate output, then - # look for what the output results url was. - - tmpdir = tempfile.mkdtemp() - res, out, err, user = logging_run(['--results-directory=' + tmpdir], - tests_included=True) - self.assertEqual(user.url, os.path.join(tmpdir, 'results.html')) - shutil.rmtree(tmpdir, ignore_errors=True) - - def test_results_directory_default(self): - # We run a configuration that should fail, to generate output, then - # look for what the output results url was. - - # This is the default location. - res, out, err, user = logging_run(tests_included=True) - self.assertEqual(user.url, '/tmp/layout-test-results/results.html') - - def test_results_directory_relative(self): - # We run a configuration that should fail, to generate output, then - # look for what the output results url was. - - res, out, err, user = logging_run(['--results-directory=foo'], - tests_included=True) - self.assertEqual(user.url, '/tmp/foo/results.html') - - def test_tolerance(self): - class ImageDiffTestPort(TestPort): - def diff_image(self, expected_contents, actual_contents, - diff_filename=None): - self.tolerance_used_for_diff_image = self._options.tolerance - return True - - def get_port_for_run(args): - options, parsed_args = run_webkit_tests.parse_args(args) - test_port = ImageDiffTestPort(options=options, user=MockUser()) - passing_run(args, port_obj=test_port, tests_included=True) - return test_port - - base_args = ['--pixel-tests', 'failures/expected/*'] - - # If we pass in an explicit tolerance argument, then that will be used. - test_port = get_port_for_run(base_args + ['--tolerance', '.1']) - self.assertEqual(0.1, test_port.tolerance_used_for_diff_image) - test_port = get_port_for_run(base_args + ['--tolerance', '0']) - self.assertEqual(0, test_port.tolerance_used_for_diff_image) - - # Otherwise the port's default tolerance behavior (including ignoring it) - # should be used. - test_port = get_port_for_run(base_args) - self.assertEqual(None, test_port.tolerance_used_for_diff_image) - - def test_worker_model__inline(self): - self.assertTrue(passing_run(['--worker-model', 'inline'])) - - def test_worker_model__threads(self): - self.assertTrue(passing_run(['--worker-model', 'threads'])) - - def test_worker_model__processes(self): - self.assertRaises(ValueError, logging_run, - ['--worker-model', 'processes']) - - def test_worker_model__unknown(self): - self.assertRaises(ValueError, logging_run, - ['--worker-model', 'unknown']) - -MainTest = skip_if(MainTest, sys.platform == 'cygwin' and compare_version(sys, '2.6')[0] < 0, 'new-run-webkit-tests tests hang on Cygwin Python 2.5.2') - - - -def _mocked_open(original_open, file_list): - def _wrapper(name, mode, encoding): - if name.find("-expected.") != -1 and mode.find("w") != -1: - # we don't want to actually write new baselines, so stub these out - name.replace('\\', '/') - file_list.append(name) - return original_open(os.devnull, mode, encoding) - return original_open(name, mode, encoding) - return _wrapper - - -class RebaselineTest(unittest.TestCase): - def assertBaselines(self, file_list, file): - "assert that the file_list contains the baselines.""" - for ext in [".txt", ".png", ".checksum"]: - baseline = file + "-expected" + ext - self.assertTrue(any(f.find(baseline) != -1 for f in file_list)) - - # FIXME: Add tests to ensure that we're *not* writing baselines when we're not - # supposed to be. - - def disabled_test_reset_results(self): - # FIXME: This test is disabled until we can rewrite it to use a - # mock filesystem. - # - # Test that we update expectations in place. If the expectation - # is missing, update the expected generic location. - file_list = [] - passing_run(['--pixel-tests', - '--reset-results', - 'passes/image.html', - 'failures/expected/missing_image.html'], - tests_included=True) - self.assertEqual(len(file_list), 6) - self.assertBaselines(file_list, - "data/passes/image") - self.assertBaselines(file_list, - "data/failures/expected/missing_image") - - def disabled_test_new_baseline(self): - # FIXME: This test is disabled until we can rewrite it to use a - # mock filesystem. - # - # Test that we update the platform expectations. If the expectation - # is mssing, then create a new expectation in the platform dir. - file_list = [] - original_open = codecs.open - try: - # Test that we update the platform expectations. If the expectation - # is mssing, then create a new expectation in the platform dir. - file_list = [] - codecs.open = _mocked_open(original_open, file_list) - passing_run(['--pixel-tests', - '--new-baseline', - 'passes/image.html', - 'failures/expected/missing_image.html'], - tests_included=True) - self.assertEqual(len(file_list), 6) - self.assertBaselines(file_list, - "data/platform/test/passes/image") - self.assertBaselines(file_list, - "data/platform/test/failures/expected/missing_image") - finally: - codecs.open = original_open - - -class TestRunnerWrapper(run_webkit_tests.TestRunner): - def _get_test_input_for_file(self, test_file): - return test_file - - -class TestRunnerTest(unittest.TestCase): - def test_results_html(self): - mock_port = Mock() - mock_port.relative_test_filename = lambda name: name - mock_port.filename_to_uri = lambda name: name - - runner = run_webkit_tests.TestRunner(port=mock_port, options=Mock(), - printer=Mock(), message_broker=Mock()) - expected_html = u"""<html> - <head> - <title>Layout Test Results (time)</title> - </head> - <body> - <h2>Title (time)</h2> - <p><a href='test_path'>test_path</a><br /> -</p> -</body></html> -""" - html = runner._results_html(["test_path"], {}, "Title", override_time="time") - self.assertEqual(html, expected_html) - - def test_shard_tests(self): - # Test that _shard_tests in run_webkit_tests.TestRunner really - # put the http tests first in the queue. - runner = TestRunnerWrapper(port=Mock(), options=Mock(), - printer=Mock(), message_broker=Mock()) - - test_list = [ - "LayoutTests/websocket/tests/unicode.htm", - "LayoutTests/animations/keyframes.html", - "LayoutTests/http/tests/security/view-source-no-refresh.html", - "LayoutTests/websocket/tests/websocket-protocol-ignored.html", - "LayoutTests/fast/css/display-none-inline-style-change-crash.html", - "LayoutTests/http/tests/xmlhttprequest/supported-xml-content-types.html", - "LayoutTests/dom/html/level2/html/HTMLAnchorElement03.html", - "LayoutTests/ietestcenter/Javascript/11.1.5_4-4-c-1.html", - "LayoutTests/dom/html/level2/html/HTMLAnchorElement06.html", - ] - - expected_tests_to_http_lock = set([ - 'LayoutTests/websocket/tests/unicode.htm', - 'LayoutTests/http/tests/security/view-source-no-refresh.html', - 'LayoutTests/websocket/tests/websocket-protocol-ignored.html', - 'LayoutTests/http/tests/xmlhttprequest/supported-xml-content-types.html', - ]) - - # FIXME: Ideally the HTTP tests don't have to all be in one shard. - single_thread_results = runner._shard_tests(test_list, False) - multi_thread_results = runner._shard_tests(test_list, True) - - self.assertEqual("tests_to_http_lock", single_thread_results[0][0]) - self.assertEqual(expected_tests_to_http_lock, set(single_thread_results[0][1])) - self.assertEqual("tests_to_http_lock", multi_thread_results[0][0]) - self.assertEqual(expected_tests_to_http_lock, set(multi_thread_results[0][1])) - - -class DryrunTest(unittest.TestCase): - # FIXME: it's hard to know which platforms are safe to test; the - # chromium platforms require a chromium checkout, and the mac platform - # requires fcntl, so it can't be tested on win32, etc. There is - # probably a better way of handling this. - def test_darwin(self): - if sys.platform != "darwin": - return - - self.assertTrue(passing_run(['--platform', 'test'])) - self.assertTrue(passing_run(['--platform', 'dryrun', - 'fast/html'])) - self.assertTrue(passing_run(['--platform', 'dryrun-mac', - 'fast/html'])) - - def test_test(self): - self.assertTrue(passing_run(['--platform', 'dryrun-test', - '--pixel-tests'])) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/__init__.py b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/__init__.py +++ /dev/null diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py deleted file mode 100644 index da466c8..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/image_diff.py +++ /dev/null @@ -1,146 +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. - -"""Compares the image output of a test to the expected image output. - -Compares hashes for the generated and expected images. If the output doesn't -match, returns FailureImageHashMismatch and outputs both hashes into the layout -test results directory. -""" - -from __future__ import with_statement - -import codecs -import errno -import logging -import os -import shutil - -from webkitpy.layout_tests.layout_package import test_failures -from webkitpy.layout_tests.test_types import test_type_base - -# Cache whether we have the image_diff executable available. -_compare_available = True -_compare_msg_printed = False - -_log = logging.getLogger("webkitpy.layout_tests.test_types.image_diff") - - -class ImageDiff(test_type_base.TestTypeBase): - - def _save_baseline_files(self, filename, image, image_hash, - generate_new_baseline): - """Saves new baselines for the PNG and checksum. - - Args: - filename: test filename - image: a image output - image_hash: a checksum of the image - generate_new_baseline: whether to generate a new, platform-specific - baseline, or update the existing one - """ - self._save_baseline_data(filename, image, ".png", encoding=None, - generate_new_baseline=generate_new_baseline) - self._save_baseline_data(filename, image_hash, ".checksum", - encoding="ascii", - generate_new_baseline=generate_new_baseline) - - def _copy_image(self, filename, actual_image, expected_image): - self.write_output_files(filename, '.png', - output=actual_image, expected=expected_image, - encoding=None, print_text_diffs=False) - - def _copy_image_hash(self, filename, actual_image_hash, expected_image_hash): - self.write_output_files(filename, '.checksum', - actual_image_hash, expected_image_hash, - encoding="ascii", print_text_diffs=False) - - def _create_diff_image(self, port, filename, actual_image, expected_image): - """Creates the visual diff of the expected/actual PNGs. - - Returns True if the images are different. - """ - diff_filename = self.output_filename(filename, - self.FILENAME_SUFFIX_COMPARE) - return port.diff_image(actual_image, expected_image, diff_filename) - - def compare_output(self, port, filename, test_args, actual_test_output, - expected_test_output): - """Implementation of CompareOutput that checks the output image and - checksum against the expected files from the LayoutTest directory. - """ - failures = [] - - # If we didn't produce a hash file, this test must be text-only. - if actual_test_output.image_hash is None: - return failures - - # If we're generating a new baseline, we pass. - if test_args.new_baseline or test_args.reset_results: - self._save_baseline_files(filename, actual_test_output.image, - actual_test_output.image_hash, - test_args.new_baseline) - return failures - - if not expected_test_output.image: - # Report a missing expected PNG file. - self._copy_image(filename, actual_test_output.image, expected_image=None) - self._copy_image_hash(filename, actual_test_output.image_hash, - expected_test_output.image_hash) - failures.append(test_failures.FailureMissingImage()) - return failures - if not expected_test_output.image_hash: - # Report a missing expected checksum file. - self._copy_image(filename, actual_test_output.image, - expected_test_output.image) - self._copy_image_hash(filename, actual_test_output.image_hash, - expected_image_hash=None) - failures.append(test_failures.FailureMissingImageHash()) - return failures - - if actual_test_output.image_hash == expected_test_output.image_hash: - # Hash matched (no diff needed, okay to return). - return failures - - self._copy_image(filename, actual_test_output.image, - expected_test_output.image) - self._copy_image_hash(filename, actual_test_output.image_hash, - expected_test_output.image_hash) - - # Even though we only use the result in one codepath below but we - # still need to call CreateImageDiff for other codepaths. - images_are_different = self._create_diff_image(port, filename, - actual_test_output.image, - expected_test_output.image) - if not images_are_different: - failures.append(test_failures.FailureImageHashIncorrect()) - else: - failures.append(test_failures.FailureImageHashMismatch()) - - return failures diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py deleted file mode 100644 index 4b96b3a..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base.py +++ /dev/null @@ -1,223 +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. - -"""Defines the interface TestTypeBase which other test types inherit from. - -Also defines the TestArguments "struct" to pass them additional arguments. -""" - -from __future__ import with_statement - -import codecs -import cgi -import errno -import logging -import os.path - -_log = logging.getLogger("webkitpy.layout_tests.test_types.test_type_base") - - -class TestArguments(object): - """Struct-like wrapper for additional arguments needed by - specific tests.""" - # Whether to save new baseline results. - new_baseline = False - - # Path to the actual PNG file generated by pixel tests - png_path = None - - # Value of checksum generated by pixel tests. - hash = None - - # Whether to use wdiff to generate by-word diffs. - wdiff = False - -# Python bug workaround. See the wdiff code in WriteOutputFiles for an -# explanation. -_wdiff_available = True - - -class TestTypeBase(object): - - # Filename pieces when writing failures to the test results directory. - FILENAME_SUFFIX_ACTUAL = "-actual" - FILENAME_SUFFIX_EXPECTED = "-expected" - FILENAME_SUFFIX_DIFF = "-diff" - FILENAME_SUFFIX_WDIFF = "-wdiff.html" - FILENAME_SUFFIX_PRETTY_PATCH = "-pretty-diff.html" - FILENAME_SUFFIX_COMPARE = "-diff.png" - - def __init__(self, port, root_output_dir): - """Initialize a TestTypeBase object. - - Args: - port: object implementing port-specific information and methods - root_output_dir: The unix style path to the output dir. - """ - self._root_output_dir = root_output_dir - self._port = port - - def _make_output_directory(self, filename): - """Creates the output directory (if needed) for a given test - filename.""" - output_filename = os.path.join(self._root_output_dir, - self._port.relative_test_filename(filename)) - self._port.maybe_make_directory(os.path.split(output_filename)[0]) - - def _save_baseline_data(self, filename, data, modifier, encoding, - generate_new_baseline=True): - """Saves a new baseline file into the port's baseline directory. - - The file will be named simply "<test>-expected<modifier>", suitable for - use as the expected results in a later run. - - Args: - filename: path to the test file - data: result to be saved as the new baseline - modifier: type of the result file, e.g. ".txt" or ".png" - encoding: file encoding (none, "utf-8", etc.) - generate_new_baseline: whether to enerate a new, platform-specific - baseline, or update the existing one - """ - - if generate_new_baseline: - relative_dir = os.path.dirname( - self._port.relative_test_filename(filename)) - baseline_path = self._port.baseline_path() - output_dir = os.path.join(baseline_path, relative_dir) - output_file = os.path.basename(os.path.splitext(filename)[0] + - self.FILENAME_SUFFIX_EXPECTED + modifier) - self._port.maybe_make_directory(output_dir) - output_path = os.path.join(output_dir, output_file) - _log.debug('writing new baseline result "%s"' % (output_path)) - else: - output_path = self._port.expected_filename(filename, modifier) - _log.debug('resetting baseline result "%s"' % output_path) - - self._port.update_baseline(output_path, data, encoding) - - def output_filename(self, filename, modifier): - """Returns a filename inside the output dir that contains modifier. - - For example, if filename is c:/.../fast/dom/foo.html and modifier is - "-expected.txt", the return value is - c:/cygwin/tmp/layout-test-results/fast/dom/foo-expected.txt - - Args: - filename: absolute filename to test file - modifier: a string to replace the extension of filename with - - Return: - The absolute windows path to the output filename - """ - output_filename = os.path.join(self._root_output_dir, - self._port.relative_test_filename(filename)) - return os.path.splitext(output_filename)[0] + modifier - - def compare_output(self, port, filename, test_args, actual_test_output, - expected_test_output): - """Method that compares the output from the test with the - expected value. - - This is an abstract method to be implemented by all sub classes. - - Args: - port: object implementing port-specific information and methods - filename: absolute filename to test file - test_args: a TestArguments object holding optional additional - arguments - actual_test_output: a TestOutput object which represents actual test - output - expected_test_output: a TestOutput object which represents a expected - test output - - Return: - a list of TestFailure objects, empty if the test passes - """ - raise NotImplementedError - - def _write_into_file_at_path(self, file_path, contents, encoding): - """This method assumes that byte_array is already encoded - into the right format.""" - open_mode = 'w' - if encoding is None: - open_mode = 'w+b' - with codecs.open(file_path, open_mode, encoding=encoding) as file: - file.write(contents) - - def write_output_files(self, filename, file_type, - output, expected, encoding, - print_text_diffs=False): - """Writes the test output, the expected output and optionally the diff - between the two to files in the results directory. - - The full output filename of the actual, for example, will be - <filename>-actual<file_type> - For instance, - my_test-actual.txt - - Args: - filename: The test filename - file_type: A string describing the test output file type, e.g. ".txt" - output: A string containing the test output - expected: A string containing the expected test output - print_text_diffs: True for text diffs. (FIXME: We should be able to get this from the file type?) - """ - self._make_output_directory(filename) - actual_filename = self.output_filename(filename, self.FILENAME_SUFFIX_ACTUAL + file_type) - expected_filename = self.output_filename(filename, self.FILENAME_SUFFIX_EXPECTED + file_type) - # FIXME: This function is poorly designed. We should be passing in some sort of - # encoding information from the callers. - if output: - self._write_into_file_at_path(actual_filename, output, encoding) - if expected: - self._write_into_file_at_path(expected_filename, expected, encoding) - - if not output or not expected: - return - - if not print_text_diffs: - return - - # Note: We pass encoding=None for all diff writes, as we treat diff - # output as binary. Diff output may contain multiple files in - # conflicting encodings. - diff = self._port.diff_text(expected, output, expected_filename, actual_filename) - diff_filename = self.output_filename(filename, self.FILENAME_SUFFIX_DIFF + file_type) - self._write_into_file_at_path(diff_filename, diff, encoding=None) - - # Shell out to wdiff to get colored inline diffs. - wdiff = self._port.wdiff_text(expected_filename, actual_filename) - wdiff_filename = self.output_filename(filename, self.FILENAME_SUFFIX_WDIFF) - self._write_into_file_at_path(wdiff_filename, wdiff, encoding=None) - - # Use WebKit's PrettyPatch.rb to get an HTML diff. - pretty_patch = self._port.pretty_patch_text(diff_filename) - pretty_patch_filename = self.output_filename(filename, self.FILENAME_SUFFIX_PRETTY_PATCH) - self._write_into_file_at_path(pretty_patch_filename, pretty_patch, encoding=None) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base_unittest.py deleted file mode 100644 index 5dbfcb6..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/test_type_base_unittest.py +++ /dev/null @@ -1,47 +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. - -""""Tests stray tests not covered by regular code paths.""" - -import test_type_base -import unittest - -from webkitpy.thirdparty.mock import Mock - - -class Test(unittest.TestCase): - - def test_compare_output_notimplemented(self): - test_type = test_type_base.TestTypeBase(None, None) - self.assertRaises(NotImplementedError, test_type.compare_output, - None, "foo.txt", '', - test_type_base.TestArguments(), 'Debug') - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py b/WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py deleted file mode 100644 index ca4b17d..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/test_types/text_diff.py +++ /dev/null @@ -1,91 +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. - -"""Compares the text output of a test to the expected text output. - -If the output doesn't match, returns FailureTextMismatch and outputs the diff -files into the layout test results directory. -""" - -from __future__ import with_statement - -import codecs -import errno -import logging -import os.path - -from webkitpy.layout_tests.layout_package import test_failures -from webkitpy.layout_tests.test_types import test_type_base - -_log = logging.getLogger("webkitpy.layout_tests.test_types.text_diff") - - -class TestTextDiff(test_type_base.TestTypeBase): - - def _get_normalized_output_text(self, output): - # Some tests produce "\r\n" explicitly. Our system (Python/Cygwin) - # helpfully changes the "\n" to "\r\n", resulting in "\r\r\n". - norm = output.replace("\r\r\n", "\r\n").strip("\r\n").replace( - "\r\n", "\n") - return norm + "\n" - - def compare_output(self, port, filename, test_args, actual_test_output, - expected_test_output): - """Implementation of CompareOutput that checks the output text against - the expected text from the LayoutTest directory.""" - failures = [] - - # If we're generating a new baseline, we pass. - if test_args.new_baseline or test_args.reset_results: - # Although all test_shell/DumpRenderTree output should be utf-8, - # we do not ever decode it inside run-webkit-tests. For some tests - # DumpRenderTree may not output utf-8 text (e.g. webarchives). - self._save_baseline_data(filename, actual_test_output.text, - ".txt", encoding=None, - generate_new_baseline=test_args.new_baseline) - return failures - - # Normalize text to diff - actual_text = self._get_normalized_output_text(actual_test_output.text) - # Assuming expected_text is already normalized. - expected_text = expected_test_output.text - - # Write output files for new tests, too. - if port.compare_text(actual_text, expected_text): - # Text doesn't match, write output files. - self.write_output_files(filename, ".txt", actual_text, - expected_text, encoding=None, - print_text_diffs=True) - - if expected_text == '': - failures.append(test_failures.FailureMissingResult()) - else: - failures.append(test_failures.FailureTextMismatch()) - - return failures diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py b/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py deleted file mode 100755 index f4c8098..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests.py +++ /dev/null @@ -1,160 +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: -# 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 COMPUTER, INC. ``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 COMPUTER, INC. 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 glob -import logging -import optparse -import os -import re -import sys -import webkitpy.common.checkout.scm as scm - -_log = logging.getLogger("webkitpy.layout_tests." - "update-webgl-conformance-tests") - - -def remove_first_line_comment(text): - return re.compile(r'^<!--.*?-->\s*', re.DOTALL).sub('', text) - - -def translate_includes(text): - # Mapping of single filename to relative path under WebKit root. - # Assumption: these filenames are globally unique. - include_mapping = { - "js-test-style.css": "../../js/resources", - "js-test-pre.js": "../../js/resources", - "js-test-post.js": "../../js/resources", - "desktop-gl-constants.js": "resources", - } - - for filename, path in include_mapping.items(): - search = r'(?:[^"\'= ]*/)?' + re.escape(filename) - replace = os.path.join(path, filename) - text = re.sub(search, replace, text) - - return text - - -def translate_khronos_test(text): - """ - This method translates the contents of a Khronos test to a WebKit test. - """ - - translateFuncs = [ - remove_first_line_comment, - translate_includes, - ] - - for f in translateFuncs: - text = f(text) - - return text - - -def update_file(in_filename, out_dir): - # check in_filename exists - # check out_dir exists - out_filename = os.path.join(out_dir, os.path.basename(in_filename)) - - _log.debug("Processing " + in_filename) - with open(in_filename, 'r') as in_file: - with open(out_filename, 'w') as out_file: - out_file.write(translate_khronos_test(in_file.read())) - - -def update_directory(in_dir, out_dir): - for filename in glob.glob(os.path.join(in_dir, '*.html')): - update_file(os.path.join(in_dir, filename), out_dir) - - -def default_out_dir(): - current_scm = scm.detect_scm_system(os.path.dirname(sys.argv[0])) - if not current_scm: - return os.getcwd() - root_dir = current_scm.checkout_root - if not root_dir: - return os.getcwd() - out_dir = os.path.join(root_dir, "LayoutTests/fast/canvas/webgl") - if os.path.isdir(out_dir): - return out_dir - return os.getcwd() - - -def configure_logging(options): - """Configures the logging system.""" - log_fmt = '%(levelname)s: %(message)s' - log_datefmt = '%y%m%d %H:%M:%S' - log_level = logging.INFO - if options.verbose: - log_fmt = ('%(asctime)s %(filename)s:%(lineno)-4d %(levelname)s ' - '%(message)s') - log_level = logging.DEBUG - logging.basicConfig(level=log_level, format=log_fmt, - datefmt=log_datefmt) - - -def option_parser(): - usage = "usage: %prog [options] (input file or directory)" - parser = optparse.OptionParser(usage=usage) - parser.add_option('-v', '--verbose', - action='store_true', - default=False, - help='include debug-level logging') - parser.add_option('-o', '--output', - action='store', - type='string', - default=default_out_dir(), - metavar='DIR', - help='specify an output directory to place files ' - 'in [default: %default]') - return parser - - -def main(): - parser = option_parser() - (options, args) = parser.parse_args() - configure_logging(options) - - if len(args) == 0: - _log.error("Must specify an input directory or filename.") - parser.print_help() - return 1 - - in_name = args[0] - if os.path.isfile(in_name): - update_file(in_name, options.output) - elif os.path.isdir(in_name): - update_directory(in_name, options.output) - else: - _log.error("'%s' is not a directory or a file.", in_name) - return 2 - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests_unittest.py b/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests_unittest.py deleted file mode 100644 index 7393b70..0000000 --- a/WebKitTools/Scripts/webkitpy/layout_tests/update_webgl_conformance_tests_unittest.py +++ /dev/null @@ -1,102 +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 update_webgl_conformance_tests.""" - -import unittest -from webkitpy.layout_tests import update_webgl_conformance_tests as webgl - - -def construct_script(name): - return "<script src=\"" + name + "\"></script>\n" - - -def construct_style(name): - return "<link rel=\"stylesheet\" href=\"" + name + "\">" - - -class TestTranslation(unittest.TestCase): - def assert_unchanged(self, text): - self.assertEqual(text, webgl.translate_khronos_test(text)) - - def assert_translate(self, input, output): - self.assertEqual(output, webgl.translate_khronos_test(input)) - - def test_simple_unchanged(self): - self.assert_unchanged("") - self.assert_unchanged("<html></html>") - - def test_header_strip(self): - single_line_header = "<!-- single line header. -->" - multi_line_header = """<!-- this is a multi-line - header. it should all be removed too. - -->""" - text = "<html></html>" - self.assert_translate(single_line_header, "") - self.assert_translate(single_line_header + text, text) - self.assert_translate(multi_line_header + text, text) - - def dont_strip_other_headers(self): - self.assert_unchanged("<html>\n<!-- don't remove comments on other lines. -->\n</html>") - - def test_include_rewriting(self): - # Mappings to None are unchanged - styles = { - "../resources/js-test-style.css": "../../js/resources/js-test-style.css", - "fail.css": None, - "resources/stylesheet.css": None, - "../resources/style.css": None, - } - scripts = { - "../resources/js-test-pre.js": "../../js/resources/js-test-pre.js", - "../resources/js-test-post.js": "../../js/resources/js-test-post.js", - "../resources/desktop-gl-constants.js": "resources/desktop-gl-constants.js", - - "resources/shadow-offset.js": None, - "../resources/js-test-post-async.js": None, - } - - input_text = "" - output_text = "" - for input, output in styles.items(): - input_text += construct_style(input) - output_text += construct_style(output if output else input) - for input, output in scripts.items(): - input_text += construct_script(input) - output_text += construct_script(output if output else input) - - head = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">\n<html>\n<head>\n' - foot = '</head>\n<body>\n</body>\n</html>' - input_text = head + input_text + foot - output_text = head + output_text + foot - self.assert_translate(input_text, output_text) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/python24/__init__.py b/WebKitTools/Scripts/webkitpy/python24/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/python24/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/python24/versioning.py b/WebKitTools/Scripts/webkitpy/python24/versioning.py deleted file mode 100644 index 8b1f21b..0000000 --- a/WebKitTools/Scripts/webkitpy/python24/versioning.py +++ /dev/null @@ -1,133 +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 Python version checking.""" - -import logging -import sys - -_log = logging.getLogger("webkitpy.python24.versioning") - -# The minimum Python version the webkitpy package supports. -_MINIMUM_SUPPORTED_PYTHON_VERSION = "2.5" - - -def compare_version(sysmodule=None, target_version=None): - """Compare the current Python version with a target version. - - Args: - sysmodule: An object with version and version_info data attributes - used to detect the current Python version. The attributes - should have the same semantics as sys.version and - sys.version_info. This parameter should only be used - for unit testing. Defaults to sys. - target_version: A string representing the Python version to compare - the current version against. The string should have - one of the following three forms: 2, 2.5, or 2.5.3. - Defaults to the minimum version that the webkitpy - package supports. - - Returns: - A triple of (comparison, current_version, target_version). - - comparison: An integer representing the result of comparing the - current version with the target version. A positive - number means the current version is greater than the - target, 0 means they are the same, and a negative number - means the current version is less than the target. - This method compares version information only up - to the precision of the given target version. For - example, if the target version is 2.6 and the current - version is 2.5.3, this method uses 2.5 for the purposes - of comparing with the target. - current_version: A string representing the current Python version, for - example 2.5.3. - target_version: A string representing the version that the current - version was compared against, for example 2.5. - - """ - if sysmodule is None: - sysmodule = sys - if target_version is None: - target_version = _MINIMUM_SUPPORTED_PYTHON_VERSION - - # The number of version parts to compare. - precision = len(target_version.split(".")) - - # We use sys.version_info rather than sys.version since its first - # three elements are guaranteed to be integers. - current_version_info_to_compare = sysmodule.version_info[:precision] - # Convert integers to strings. - current_version_info_to_compare = map(str, current_version_info_to_compare) - current_version_to_compare = ".".join(current_version_info_to_compare) - - # Compare version strings lexicographically. - if current_version_to_compare > target_version: - comparison = 1 - elif current_version_to_compare == target_version: - comparison = 0 - else: - comparison = -1 - - # The version number portion of the current version string, for - # example "2.6.4". - current_version = sysmodule.version.split()[0] - - return (comparison, current_version, target_version) - - -# FIXME: Add a logging level parameter to allow the version message -# to be logged at levels other than WARNING, for example CRITICAL. -def check_version(log=None, sysmodule=None, target_version=None): - """Check the current Python version against a target version. - - Logs a warning message if the current version is less than the - target version. - - Args: - log: A logging.logger instance to use when logging the version warning. - Defaults to the logger of this module. - sysmodule: See the compare_version() docstring. - target_version: See the compare_version() docstring. - - Returns: - A boolean value of whether the current version is greater than - or equal to the target version. - - """ - if log is None: - log = _log - - (comparison, current_version, target_version) = \ - compare_version(sysmodule, target_version) - - if comparison >= 0: - # Then the current version is at least the minimum version. - return True - - message = ("WebKit Python scripts do not support your current Python " - "version (%s). The minimum supported version is %s.\n" - " See the following page to upgrade your Python version:\n\n" - " http://trac.webkit.org/wiki/PythonGuidelines\n" - % (current_version, target_version)) - log.warn(message) - return False diff --git a/WebKitTools/Scripts/webkitpy/python24/versioning_unittest.py b/WebKitTools/Scripts/webkitpy/python24/versioning_unittest.py deleted file mode 100644 index 6939e2d..0000000 --- a/WebKitTools/Scripts/webkitpy/python24/versioning_unittest.py +++ /dev/null @@ -1,134 +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 unit tests for versioning.py.""" - -import logging -import unittest - -from webkitpy.common.system.logtesting import LogTesting -from webkitpy.python24.versioning import check_version -from webkitpy.python24.versioning import compare_version - -class MockSys(object): - - """A mock sys module for passing to version-checking methods.""" - - def __init__(self, current_version): - """Create an instance. - - current_version: A version string with major, minor, and micro - version parts. - - """ - version_info = current_version.split(".") - version_info = map(int, version_info) - - self.version = current_version + " Version details." - self.version_info = version_info - - -class CompareVersionTest(unittest.TestCase): - - """Tests compare_version().""" - - def _mock_sys(self, current_version): - return MockSys(current_version) - - def test_default_minimum_version(self): - """Test the configured minimum version that webkitpy supports.""" - (comparison, current_version, min_version) = compare_version() - self.assertEquals(min_version, "2.5") - - def compare_version(self, target_version, current_version=None): - """Call compare_version().""" - if current_version is None: - current_version = "2.5.3" - mock_sys = self._mock_sys(current_version) - return compare_version(mock_sys, target_version) - - def compare(self, target_version, current_version=None): - """Call compare_version(), and return the comparison.""" - return self.compare_version(target_version, current_version)[0] - - def test_returned_current_version(self): - """Test the current_version return value.""" - current_version = self.compare_version("2.5")[1] - self.assertEquals(current_version, "2.5.3") - - def test_returned_target_version(self): - """Test the current_version return value.""" - target_version = self.compare_version("2.5")[2] - self.assertEquals(target_version, "2.5") - - def test_target_version_major(self): - """Test major version for target.""" - self.assertEquals(-1, self.compare("3")) - self.assertEquals(0, self.compare("2")) - self.assertEquals(1, self.compare("2", "3.0.0")) - - def test_target_version_minor(self): - """Test minor version for target.""" - self.assertEquals(-1, self.compare("2.6")) - self.assertEquals(0, self.compare("2.5")) - self.assertEquals(1, self.compare("2.4")) - - def test_target_version_micro(self): - """Test minor version for target.""" - self.assertEquals(-1, self.compare("2.5.4")) - self.assertEquals(0, self.compare("2.5.3")) - self.assertEquals(1, self.compare("2.5.2")) - - -class CheckVersionTest(unittest.TestCase): - - """Tests check_version().""" - - def setUp(self): - self._log = LogTesting.setUp(self) - - def tearDown(self): - self._log.tearDown() - - def _check_version(self, minimum_version): - """Call check_version().""" - mock_sys = MockSys("2.5.3") - return check_version(sysmodule=mock_sys, target_version=minimum_version) - - def test_true_return_value(self): - """Test the configured minimum version that webkitpy supports.""" - is_current = self._check_version("2.4") - self.assertEquals(True, is_current) - self._log.assertMessages([]) # No warning was logged. - - def test_false_return_value(self): - """Test the configured minimum version that webkitpy supports.""" - is_current = self._check_version("2.6") - self.assertEquals(False, is_current) - expected_message = ('WARNING: WebKit Python scripts do not support ' - 'your current Python version (2.5.3). ' - 'The minimum supported version is 2.6.\n ' - 'See the following page to upgrade your Python ' - 'version:\n\n ' - 'http://trac.webkit.org/wiki/PythonGuidelines\n\n') - self._log.assertMessages([expected_message]) - diff --git a/WebKitTools/Scripts/webkitpy/style/__init__.py b/WebKitTools/Scripts/webkitpy/style/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/style/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/style/checker.py b/WebKitTools/Scripts/webkitpy/style/checker.py deleted file mode 100644 index e10eec5..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checker.py +++ /dev/null @@ -1,739 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) -# Copyright (C) 2010 ProFUSION embedded systems -# -# 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. - -"""Front end of some style-checker modules.""" - -import logging -import os.path -import sys - -from checkers.common import categories as CommonCategories -from checkers.common import CarriageReturnChecker -from checkers.cpp import CppChecker -from checkers.python import PythonChecker -from checkers.test_expectations import TestExpectationsChecker -from checkers.text import TextChecker -from error_handlers import DefaultStyleErrorHandler -from filter import FilterConfiguration -from optparser import ArgumentParser -from optparser import DefaultCommandOptionValues -from webkitpy.style_references import configure_logging as _configure_logging - -_log = logging.getLogger("webkitpy.style.checker") - -# These are default option values for the command-line option parser. -_DEFAULT_MIN_CONFIDENCE = 1 -_DEFAULT_OUTPUT_FORMAT = 'emacs' - - -# FIXME: For style categories we will never want to have, remove them. -# For categories for which we want to have similar functionality, -# modify the implementation and enable them. -# -# Throughout this module, we use "filter rule" rather than "filter" -# for an individual boolean filter flag like "+foo". This allows us to -# reserve "filter" for what one gets by collectively applying all of -# the filter rules. -# -# The base filter rules are the filter rules that begin the list of -# filter rules used to check style. For example, these rules precede -# any user-specified filter rules. Since by default all categories are -# checked, this list should normally include only rules that begin -# with a "-" sign. -_BASE_FILTER_RULES = [ - '-build/endif_comment', - '-build/include_what_you_use', # <string> for std::string - '-build/storage_class', # const static - '-legal/copyright', - '-readability/multiline_comment', - '-readability/braces', # int foo() {}; - '-readability/fn_size', - '-readability/casting', - '-readability/function', - '-runtime/arrays', # variable length array - '-runtime/casting', - '-runtime/sizeof', - '-runtime/explicit', # explicit - '-runtime/virtual', # virtual dtor - '-runtime/printf', - '-runtime/threadsafe_fn', - '-runtime/rtti', - '-whitespace/blank_line', - '-whitespace/end_of_line', - '-whitespace/labels', - # List Python pep8 categories last. - # - # Because much of WebKit's Python code base does not abide by the - # PEP8 79 character limit, we ignore the 79-character-limit category - # pep8/E501 for now. - # - # FIXME: Consider bringing WebKit's Python code base into conformance - # with the 79 character limit, or some higher limit that is - # agreeable to the WebKit project. - '-pep8/E501', - ] - - -# The path-specific filter rules. -# -# This list is order sensitive. Only the first path substring match -# is used. See the FilterConfiguration documentation in filter.py -# for more information on this list. -# -# Each string appearing in this nested list should have at least -# one associated unit test assertion. These assertions are located, -# for example, in the test_path_rules_specifier() unit test method of -# checker_unittest.py. -_PATH_RULES_SPECIFIER = [ - # Files in these directories are consumers of the WebKit - # API and therefore do not follow the same header including - # discipline as WebCore. - - ([# TestNetscapePlugIn has no config.h and uses funny names like - # NPP_SetWindow. - "WebKitTools/DumpRenderTree/TestNetscapePlugIn/", - # The API test harnesses have no config.h and use funny macros like - # TEST_CLASS_NAME. - "WebKitTools/WebKitAPITest/", - "WebKitTools/TestWebKitAPI/"], - ["-build/include", - "-readability/naming"]), - ([# The EFL APIs use EFL naming style, which includes - # both lower-cased and camel-cased, underscore-sparated - # values. - "WebKit/efl/ewk/", - # There is no clean way to avoid "yy_*" names used by flex. - "WebCore/css/CSSParser.cpp", - # Qt code uses '_' in some places (such as private slots - # and on test xxx_data methos on tests) - "JavaScriptCore/qt/api/", - "WebKit/qt/Api/", - "WebKit/qt/tests/", - "WebKit/qt/declarative/", - "WebKit/qt/examples/"], - ["-readability/naming"]), - ([# The GTK+ APIs use GTK+ naming style, which includes - # lower-cased, underscore-separated values. - # Also, GTK+ allows the use of NULL. - "WebCore/bindings/scripts/test/GObject", - "WebKit/gtk/webkit/", - "WebKitTools/DumpRenderTree/gtk/"], - ["-readability/naming", - "-readability/null"]), - ([# Header files in ForwardingHeaders have no header guards or - # exceptional header guards (e.g., WebCore_FWD_Debugger_h). - "/ForwardingHeaders/"], - ["-build/header_guard"]), - ([# assembler has lots of opcodes that use underscores, so - # we don't check for underscores in that directory. - "/JavaScriptCore/assembler/"], - ["-readability/naming"]), - - # WebKit2 rules: - # WebKit2 doesn't use config.h, and certain directories have other - # idiosyncracies. - ([# NPAPI has function names with underscores. - "WebKit2/WebProcess/Plugins/Netscape"], - ["-build/include_order", - "-readability/naming"]), - ([# The WebKit2 C API has names with underscores and whitespace-aligned - # struct members. - "WebKit2/UIProcess/API/C/", - "WebKit2/WebProcess/InjectedBundle/API/c/"], - ["-build/include_order", - "-readability/naming", - "-whitespace/declaration"]), - ([# Nothing in WebKit2 uses config.h. - "WebKit2/"], - ["-build/include_order"]), - - # For third-party Python code, keep only the following checks-- - # - # No tabs: to avoid having to set the SVN allow-tabs property. - # No trailing white space: since this is easy to correct. - # No carriage-return line endings: since this is easy to correct. - # - (["webkitpy/thirdparty/"], - ["-", - "+pep8/W191", # Tabs - "+pep8/W291", # Trailing white space - "+whitespace/carriage_return"]), -] - - -_CPP_FILE_EXTENSIONS = [ - 'c', - 'cpp', - 'h', - ] - -_PYTHON_FILE_EXTENSION = 'py' - -# FIXME: Include 'vcproj' files as text files after creating a mechanism -# for exempting them from the carriage-return checker (since they -# are Windows-only files). -_TEXT_FILE_EXTENSIONS = [ - 'ac', - 'cc', - 'cgi', - 'css', - 'exp', - 'flex', - 'gyp', - 'gypi', - 'html', - 'idl', - 'in', - 'js', - 'mm', - 'php', - 'pl', - 'pm', - 'pri', - 'pro', - 'rb', - 'sh', - 'txt', -# 'vcproj', # See FIXME above. - 'wm', - 'xhtml', - 'y', - ] - - -# Files to skip that are less obvious. -# -# Some files should be skipped when checking style. For example, -# WebKit maintains some files in Mozilla style on purpose to ease -# future merges. -_SKIPPED_FILES_WITH_WARNING = [ - "gtk2drawing.c", # WebCore/platform/gtk/gtk2drawing.c - "gtkdrawing.h", # WebCore/platform/gtk/gtkdrawing.h - "WebKit/gtk/tests/", - # Soup API that is still being cooked, will be removed from WebKit - # in a few months when it is merged into soup proper. The style - # follows the libsoup style completely. - "WebCore/platform/network/soup/cache/", - ] - - -# Files to skip that are more common or obvious. -# -# This list should be in addition to files with FileType.NONE. Files -# with FileType.NONE are automatically skipped without warning. -_SKIPPED_FILES_WITHOUT_WARNING = [ - "LayoutTests/", - ] - - -# The maximum number of errors to report per file, per category. -# If a category is not a key, then it has no maximum. -_MAX_REPORTS_PER_CATEGORY = { - "whitespace/carriage_return": 1 -} - - -def _all_categories(): - """Return the set of all categories used by check-webkit-style.""" - # Take the union across all checkers. - categories = CommonCategories.union(CppChecker.categories) - categories = categories.union(TestExpectationsChecker.categories) - - # FIXME: Consider adding all of the pep8 categories. Since they - # are not too meaningful for documentation purposes, for - # now we add only the categories needed for the unit tests - # (which validate the consistency of the configuration - # settings against the known categories, etc). - categories = categories.union(["pep8/W191", "pep8/W291", "pep8/E501"]) - - return categories - - -def _check_webkit_style_defaults(): - """Return the default command-line options for check-webkit-style.""" - return DefaultCommandOptionValues(min_confidence=_DEFAULT_MIN_CONFIDENCE, - output_format=_DEFAULT_OUTPUT_FORMAT) - - -# This function assists in optparser not having to import from checker. -def check_webkit_style_parser(): - all_categories = _all_categories() - default_options = _check_webkit_style_defaults() - return ArgumentParser(all_categories=all_categories, - base_filter_rules=_BASE_FILTER_RULES, - default_options=default_options) - - -def check_webkit_style_configuration(options): - """Return a StyleProcessorConfiguration instance for check-webkit-style. - - Args: - options: A CommandOptionValues instance. - - """ - filter_configuration = FilterConfiguration( - base_rules=_BASE_FILTER_RULES, - path_specific=_PATH_RULES_SPECIFIER, - user_rules=options.filter_rules) - - return StyleProcessorConfiguration(filter_configuration=filter_configuration, - max_reports_per_category=_MAX_REPORTS_PER_CATEGORY, - min_confidence=options.min_confidence, - output_format=options.output_format, - stderr_write=sys.stderr.write) - - -def _create_log_handlers(stream): - """Create and return a default list of logging.Handler instances. - - Format WARNING messages and above to display the logging level, and - messages strictly below WARNING not to display it. - - Args: - stream: See the configure_logging() docstring. - - """ - # Handles logging.WARNING and above. - error_handler = logging.StreamHandler(stream) - error_handler.setLevel(logging.WARNING) - formatter = logging.Formatter("%(levelname)s: %(message)s") - error_handler.setFormatter(formatter) - - # Create a logging.Filter instance that only accepts messages - # below WARNING (i.e. filters out anything WARNING or above). - non_error_filter = logging.Filter() - # The filter method accepts a logging.LogRecord instance. - non_error_filter.filter = lambda record: record.levelno < logging.WARNING - - non_error_handler = logging.StreamHandler(stream) - non_error_handler.addFilter(non_error_filter) - formatter = logging.Formatter("%(message)s") - non_error_handler.setFormatter(formatter) - - return [error_handler, non_error_handler] - - -def _create_debug_log_handlers(stream): - """Create and return a list of logging.Handler instances for debugging. - - Args: - stream: See the configure_logging() docstring. - - """ - handler = logging.StreamHandler(stream) - formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s") - handler.setFormatter(formatter) - - return [handler] - - -def configure_logging(stream, logger=None, is_verbose=False): - """Configure logging, and return the list of handlers added. - - 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: - stream: A file-like object to which to log. The stream must - define an "encoding" data attribute, or else logging - raises an error. - logger: A logging.logger instance to configure. This parameter - should be used only in unit tests. Defaults to the - root logger. - is_verbose: A boolean value of whether logging should be verbose. - - """ - # 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 logger is None: - logger = logging.getLogger() - - if is_verbose: - logging_level = logging.DEBUG - handlers = _create_debug_log_handlers(stream) - else: - logging_level = logging.INFO - handlers = _create_log_handlers(stream) - - handlers = _configure_logging(logging_level=logging_level, logger=logger, - handlers=handlers) - - return handlers - - -# Enum-like idiom -class FileType: - - NONE = 0 # FileType.NONE evaluates to False. - # Alphabetize remaining types - CPP = 1 - PYTHON = 2 - TEXT = 3 - - -class CheckerDispatcher(object): - - """Supports determining whether and how to check style, based on path.""" - - def _file_extension(self, file_path): - """Return the file extension without the leading dot.""" - return os.path.splitext(file_path)[1].lstrip(".") - - def should_skip_with_warning(self, file_path): - """Return whether the given file should be skipped with a warning.""" - for skipped_file in _SKIPPED_FILES_WITH_WARNING: - if file_path.find(skipped_file) >= 0: - return True - return False - - def should_skip_without_warning(self, file_path): - """Return whether the given file should be skipped without a warning.""" - if not self._file_type(file_path): # FileType.NONE. - return True - # Since "LayoutTests" is in _SKIPPED_FILES_WITHOUT_WARNING, make - # an exception to prevent files like "LayoutTests/ChangeLog" and - # "LayoutTests/ChangeLog-2009-06-16" from being skipped. - # Files like 'test_expectations.txt' and 'drt_expectations.txt' - # are also should not be skipped. - # - # FIXME: Figure out a good way to avoid having to add special logic - # for this special case. - basename = os.path.basename(file_path) - if basename.startswith('ChangeLog'): - return False - elif basename == 'test_expectations.txt' or basename == 'drt_expectations.txt': - return False - for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING: - if file_path.find(skipped_file) >= 0: - return True - return False - - def _file_type(self, file_path): - """Return the file type corresponding to the given file.""" - file_extension = self._file_extension(file_path) - - if (file_extension in _CPP_FILE_EXTENSIONS) or (file_path == '-'): - # FIXME: Do something about the comment below and the issue it - # raises since cpp_style already relies on the extension. - # - # Treat stdin as C++. Since the extension is unknown when - # reading from stdin, cpp_style tests should not rely on - # the extension. - return FileType.CPP - elif file_extension == _PYTHON_FILE_EXTENSION: - return FileType.PYTHON - elif (os.path.basename(file_path).startswith('ChangeLog') or - (not file_extension and "WebKitTools/Scripts/" in file_path) or - file_extension in _TEXT_FILE_EXTENSIONS): - return FileType.TEXT - else: - return FileType.NONE - - def _create_checker(self, file_type, file_path, handle_style_error, - min_confidence): - """Instantiate and return a style checker based on file type.""" - if file_type == FileType.NONE: - checker = None - elif file_type == FileType.CPP: - file_extension = self._file_extension(file_path) - checker = CppChecker(file_path, file_extension, - handle_style_error, min_confidence) - elif file_type == FileType.PYTHON: - checker = PythonChecker(file_path, handle_style_error) - elif file_type == FileType.TEXT: - basename = os.path.basename(file_path) - if basename == 'test_expectations.txt' or basename == 'drt_expectations.txt': - checker = TestExpectationsChecker(file_path, handle_style_error) - else: - checker = TextChecker(file_path, handle_style_error) - else: - raise ValueError('Invalid file type "%(file_type)s": the only valid file types ' - "are %(NONE)s, %(CPP)s, and %(TEXT)s." - % {"file_type": file_type, - "NONE": FileType.NONE, - "CPP": FileType.CPP, - "TEXT": FileType.TEXT}) - - return checker - - def dispatch(self, file_path, handle_style_error, min_confidence): - """Instantiate and return a style checker based on file path.""" - file_type = self._file_type(file_path) - - checker = self._create_checker(file_type, - file_path, - handle_style_error, - min_confidence) - return checker - - -# FIXME: Remove the stderr_write attribute from this class and replace -# its use with calls to a logging module logger. -class StyleProcessorConfiguration(object): - - """Stores configuration values for the StyleProcessor class. - - Attributes: - min_confidence: An integer between 1 and 5 inclusive that is the - minimum confidence level of style errors to report. - - max_reports_per_category: The maximum number of errors to report - per category, per file. - - stderr_write: A function that takes a string as a parameter and - serves as stderr.write. - - """ - - def __init__(self, - filter_configuration, - max_reports_per_category, - min_confidence, - output_format, - stderr_write): - """Create a StyleProcessorConfiguration instance. - - Args: - filter_configuration: A FilterConfiguration instance. The default - is the "empty" filter configuration, which - means that all errors should be checked. - - max_reports_per_category: The maximum number of errors to report - per category, per file. - - min_confidence: An integer between 1 and 5 inclusive that is the - minimum confidence level of style errors to report. - The default is 1, which reports all style errors. - - output_format: A string that is the output format. The supported - output formats are "emacs" which emacs can parse - and "vs7" which Microsoft Visual Studio 7 can parse. - - stderr_write: A function that takes a string as a parameter and - serves as stderr.write. - - """ - self._filter_configuration = filter_configuration - self._output_format = output_format - - self.max_reports_per_category = max_reports_per_category - self.min_confidence = min_confidence - self.stderr_write = stderr_write - - def is_reportable(self, category, confidence_in_error, file_path): - """Return whether an error is reportable. - - An error is reportable if both the confidence in the error is - at least the minimum confidence level and the current filter - says the category should be checked for the given path. - - Args: - category: A string that is a style category. - confidence_in_error: An integer between 1 and 5 inclusive that is - the application's confidence in the error. - A higher number means greater confidence. - file_path: The path of the file being checked - - """ - if confidence_in_error < self.min_confidence: - return False - - return self._filter_configuration.should_check(category, file_path) - - def write_style_error(self, - category, - confidence_in_error, - file_path, - line_number, - message): - """Write a style error to the configured stderr.""" - if self._output_format == 'vs7': - format_string = "%s(%s): %s [%s] [%d]\n" - else: - format_string = "%s:%s: %s [%s] [%d]\n" - - self.stderr_write(format_string % (file_path, - line_number, - message, - category, - confidence_in_error)) - - -class ProcessorBase(object): - - """The base class for processors of lists of lines.""" - - def should_process(self, file_path): - """Return whether the file at file_path should be processed. - - The TextFileReader class calls this method prior to reading in - the lines of a file. Use this method, for example, to prevent - the style checker from reading binary files into memory. - - """ - raise NotImplementedError('Subclasses should implement.') - - def process(self, lines, file_path, **kwargs): - """Process lines of text read from a file. - - Args: - lines: A list of lines of text to process. - file_path: The path from which the lines were read. - **kwargs: This argument signifies that the process() method of - subclasses of ProcessorBase may support additional - keyword arguments. - For example, a style checker's check() method - may support a "reportable_lines" parameter that represents - the line numbers of the lines for which style errors - should be reported. - - """ - raise NotImplementedError('Subclasses should implement.') - - -class StyleProcessor(ProcessorBase): - - """A ProcessorBase for checking style. - - Attributes: - error_count: An integer that is the total number of reported - errors for the lifetime of this instance. - - """ - - def __init__(self, configuration, mock_dispatcher=None, - mock_increment_error_count=None, - mock_carriage_checker_class=None): - """Create an instance. - - Args: - configuration: A StyleProcessorConfiguration instance. - mock_dispatcher: A mock CheckerDispatcher instance. This - parameter is for unit testing. Defaults to a - CheckerDispatcher instance. - mock_increment_error_count: A mock error-count incrementer. - mock_carriage_checker_class: A mock class for checking and - transforming carriage returns. - This parameter is for unit testing. - Defaults to CarriageReturnChecker. - - """ - if mock_dispatcher is None: - dispatcher = CheckerDispatcher() - else: - dispatcher = mock_dispatcher - - if mock_increment_error_count is None: - # The following blank line is present to avoid flagging by pep8.py. - - def increment_error_count(): - """Increment the total count of reported errors.""" - self.error_count += 1 - else: - increment_error_count = mock_increment_error_count - - if mock_carriage_checker_class is None: - # This needs to be a class rather than an instance since the - # process() method instantiates one using parameters. - carriage_checker_class = CarriageReturnChecker - else: - carriage_checker_class = mock_carriage_checker_class - - self.error_count = 0 - - self._carriage_checker_class = carriage_checker_class - self._configuration = configuration - self._dispatcher = dispatcher - self._increment_error_count = increment_error_count - - def should_process(self, file_path): - """Return whether the file should be checked for style.""" - if self._dispatcher.should_skip_without_warning(file_path): - return False - if self._dispatcher.should_skip_with_warning(file_path): - _log.warn('File exempt from style guide. Skipping: "%s"' - % file_path) - return False - return True - - def process(self, lines, file_path, line_numbers=None): - """Check the given lines for style. - - Arguments: - lines: A list of all lines in the file to check. - file_path: The path of the file to process. If possible, the path - should be relative to the source root. Otherwise, - path-specific logic may not behave as expected. - line_numbers: A list of line numbers of the lines for which - style errors should be reported, or None if errors - for all lines should be reported. When not None, this - list normally contains the line numbers corresponding - to the modified lines of a patch. - - """ - _log.debug("Checking style: " + file_path) - - style_error_handler = DefaultStyleErrorHandler( - configuration=self._configuration, - file_path=file_path, - increment_error_count=self._increment_error_count, - line_numbers=line_numbers) - - carriage_checker = self._carriage_checker_class(style_error_handler) - - # FIXME: We should probably use the SVN "eol-style" property - # or a white list to decide whether or not to do - # the carriage-return check. Originally, we did the - # check only if (os.linesep != '\r\n'). - # - # Check for and remove trailing carriage returns ("\r"). - lines = carriage_checker.check(lines) - - min_confidence = self._configuration.min_confidence - checker = self._dispatcher.dispatch(file_path, - style_error_handler, - min_confidence) - - if checker is None: - raise AssertionError("File should not be checked: '%s'" % file_path) - - _log.debug("Using class: " + checker.__class__.__name__) - - checker.check(lines) diff --git a/WebKitTools/Scripts/webkitpy/style/checker_unittest.py b/WebKitTools/Scripts/webkitpy/style/checker_unittest.py deleted file mode 100755 index 94d2c29..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checker_unittest.py +++ /dev/null @@ -1,779 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; -*- -# -# Copyright (C) 2009 Google Inc. All rights reserved. -# Copyright (C) 2009 Torch Mobile Inc. -# Copyright (C) 2009 Apple Inc. All rights reserved. -# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) -# -# 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 style.py.""" - -import logging -import os -import unittest - -import checker as style -from webkitpy.style_references import LogTesting -from webkitpy.style_references import TestLogStream -from checker import _BASE_FILTER_RULES -from checker import _MAX_REPORTS_PER_CATEGORY -from checker import _PATH_RULES_SPECIFIER as PATH_RULES_SPECIFIER -from checker import _all_categories -from checker import check_webkit_style_configuration -from checker import check_webkit_style_parser -from checker import configure_logging -from checker import CheckerDispatcher -from checker import ProcessorBase -from checker import StyleProcessor -from checker import StyleProcessorConfiguration -from checkers.cpp import CppChecker -from checkers.python import PythonChecker -from checkers.text import TextChecker -from error_handlers import DefaultStyleErrorHandler -from filter import validate_filter_rules -from filter import FilterConfiguration -from optparser import ArgumentParser -from optparser import CommandOptionValues -from webkitpy.common.system.logtesting import LoggingTestCase -from webkitpy.style.filereader import TextFileReader - - -class ConfigureLoggingTestBase(unittest.TestCase): - - """Base class for testing configure_logging(). - - Sub-classes should implement: - - is_verbose: The is_verbose value to pass to configure_logging(). - - """ - - def setUp(self): - is_verbose = self.is_verbose - - log_stream = TestLogStream(self) - - # Use a logger other than the root logger or one prefixed with - # webkit 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 - - self._handlers = configure_logging(stream=log_stream, logger=logger, - is_verbose=is_verbose) - 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 the configure_logging() function.""" - - is_verbose = False - - def test_warning_message(self): - self._log.warn("test message") - self.assert_log_messages(["WARNING: test message\n"]) - - def test_below_warning_message(self): - # We test the boundary case of a logging level equal to 29. - # In practice, we will probably only be calling log.info(), - # which corresponds to a logging level of 20. - level = logging.WARNING - 1 # Equals 29. - self._log.log(level, "test message") - self.assert_log_messages(["test message\n"]) - - def test_debug_message(self): - self._log.debug("test message") - self.assert_log_messages([]) - - def test_two_messages(self): - self._log.info("message1") - self._log.info("message2") - self.assert_log_messages(["message1\n", "message2\n"]) - - -class ConfigureLoggingVerboseTest(ConfigureLoggingTestBase): - - """Tests the configure_logging() function with is_verbose True.""" - - is_verbose = True - - def test_debug_message(self): - self._log.debug("test message") - self.assert_log_messages(["unittest: DEBUG test message\n"]) - - -class GlobalVariablesTest(unittest.TestCase): - - """Tests validity of the global variables.""" - - def _all_categories(self): - return _all_categories() - - def defaults(self): - return style._check_webkit_style_defaults() - - def test_webkit_base_filter_rules(self): - base_filter_rules = _BASE_FILTER_RULES - defaults = self.defaults() - already_seen = [] - validate_filter_rules(base_filter_rules, self._all_categories()) - # Also do some additional checks. - for rule in base_filter_rules: - # Check no leading or trailing white space. - self.assertEquals(rule, rule.strip()) - # All categories are on by default, so defaults should - # begin with -. - self.assertTrue(rule.startswith('-')) - # Check no rule occurs twice. - self.assertFalse(rule in already_seen) - already_seen.append(rule) - - def test_defaults(self): - """Check that default arguments are valid.""" - default_options = self.defaults() - - # FIXME: We should not need to call parse() to determine - # whether the default arguments are valid. - parser = ArgumentParser(all_categories=self._all_categories(), - base_filter_rules=[], - default_options=default_options) - # No need to test the return value here since we test parse() - # on valid arguments elsewhere. - # - # The default options are valid: no error or SystemExit. - parser.parse(args=[]) - - def test_path_rules_specifier(self): - all_categories = self._all_categories() - for (sub_paths, path_rules) in PATH_RULES_SPECIFIER: - validate_filter_rules(path_rules, self._all_categories()) - - config = FilterConfiguration(path_specific=PATH_RULES_SPECIFIER) - - def assertCheck(path, category): - """Assert that the given category should be checked.""" - message = ('Should check category "%s" for path "%s".' - % (category, path)) - self.assertTrue(config.should_check(category, path)) - - def assertNoCheck(path, category): - """Assert that the given category should not be checked.""" - message = ('Should not check category "%s" for path "%s".' - % (category, path)) - self.assertFalse(config.should_check(category, path), message) - - assertCheck("random_path.cpp", - "build/include") - assertNoCheck("WebKitTools/WebKitAPITest/main.cpp", - "build/include") - assertCheck("random_path.cpp", - "readability/naming") - assertNoCheck("WebKit/gtk/webkit/webkit.h", - "readability/naming") - assertNoCheck("WebKitTools/DumpRenderTree/gtk/DumpRenderTree.cpp", - "readability/null") - assertNoCheck("WebKit/efl/ewk/ewk_view.h", - "readability/naming") - assertNoCheck("WebCore/css/CSSParser.cpp", - "readability/naming") - - # Test if Qt exceptions are indeed working - assertCheck("JavaScriptCore/qt/api/qscriptengine.cpp", - "readability/braces") - assertCheck("WebKit/qt/Api/qwebpage.cpp", - "readability/braces") - assertCheck("WebKit/qt/tests/qwebelement/tst_qwebelement.cpp", - "readability/braces") - assertCheck("WebKit/qt/declarative/platformplugin/WebPlugin.cpp", - "readability/braces") - assertCheck("WebKit/qt/examples/platformplugin/WebPlugin.cpp", - "readability/braces") - assertNoCheck("JavaScriptCore/qt/api/qscriptengine.cpp", - "readability/naming") - assertNoCheck("WebKit/qt/Api/qwebpage.cpp", - "readability/naming") - assertNoCheck("WebKit/qt/tests/qwebelement/tst_qwebelement.cpp", - "readability/naming") - assertNoCheck("WebKit/qt/declarative/platformplugin/WebPlugin.cpp", - "readability/naming") - assertNoCheck("WebKit/qt/examples/platformplugin/WebPlugin.cpp", - "readability/naming") - - assertNoCheck("WebCore/ForwardingHeaders/debugger/Debugger.h", - "build/header_guard") - - # Third-party Python code: webkitpy/thirdparty - path = "WebKitTools/Scripts/webkitpy/thirdparty/mock.py" - assertNoCheck(path, "build/include") - assertNoCheck(path, "pep8/E401") # A random pep8 category. - assertCheck(path, "pep8/W191") - assertCheck(path, "pep8/W291") - assertCheck(path, "whitespace/carriage_return") - - def test_max_reports_per_category(self): - """Check that _MAX_REPORTS_PER_CATEGORY is valid.""" - all_categories = self._all_categories() - for category in _MAX_REPORTS_PER_CATEGORY.iterkeys(): - self.assertTrue(category in all_categories, - 'Key "%s" is not a category' % category) - - -class CheckWebKitStyleFunctionTest(unittest.TestCase): - - """Tests the functions with names of the form check_webkit_style_*.""" - - def test_check_webkit_style_configuration(self): - # Exercise the code path to make sure the function does not error out. - option_values = CommandOptionValues() - configuration = check_webkit_style_configuration(option_values) - - def test_check_webkit_style_parser(self): - # Exercise the code path to make sure the function does not error out. - parser = check_webkit_style_parser() - - -class CheckerDispatcherSkipTest(unittest.TestCase): - - """Tests the "should skip" methods of the CheckerDispatcher class.""" - - def setUp(self): - self._dispatcher = CheckerDispatcher() - - def test_should_skip_with_warning(self): - """Test should_skip_with_warning().""" - # Check a non-skipped file. - self.assertFalse(self._dispatcher.should_skip_with_warning("foo.txt")) - - # Check skipped files. - paths_to_skip = [ - "gtk2drawing.c", - "gtkdrawing.h", - "WebCore/platform/gtk/gtk2drawing.c", - "WebCore/platform/gtk/gtkdrawing.h", - "WebKit/gtk/tests/testatk.c", - ] - - for path in paths_to_skip: - self.assertTrue(self._dispatcher.should_skip_with_warning(path), - "Checking: " + path) - - def _assert_should_skip_without_warning(self, path, is_checker_none, - expected): - # Check the file type before asserting the return value. - checker = self._dispatcher.dispatch(file_path=path, - handle_style_error=None, - min_confidence=3) - message = 'while checking: %s' % path - self.assertEquals(checker is None, is_checker_none, message) - self.assertEquals(self._dispatcher.should_skip_without_warning(path), - expected, message) - - def test_should_skip_without_warning__true(self): - """Test should_skip_without_warning() for True return values.""" - # Check a file with NONE file type. - path = 'foo.asdf' # Non-sensical file extension. - self._assert_should_skip_without_warning(path, - is_checker_none=True, - expected=True) - - # Check files with non-NONE file type. These examples must be - # drawn from the _SKIPPED_FILES_WITHOUT_WARNING configuration - # variable. - path = os.path.join('LayoutTests', 'foo.txt') - self._assert_should_skip_without_warning(path, - is_checker_none=False, - expected=True) - - def test_should_skip_without_warning__false(self): - """Test should_skip_without_warning() for False return values.""" - paths = ['foo.txt', - os.path.join('LayoutTests', 'ChangeLog'), - ] - - for path in paths: - self._assert_should_skip_without_warning(path, - is_checker_none=False, - expected=False) - - -class CheckerDispatcherDispatchTest(unittest.TestCase): - - """Tests dispatch() method of CheckerDispatcher class.""" - - def mock_handle_style_error(self): - pass - - def dispatch(self, file_path): - """Call dispatch() with the given file path.""" - dispatcher = CheckerDispatcher() - checker = dispatcher.dispatch(file_path, - self.mock_handle_style_error, - min_confidence=3) - return checker - - def assert_checker_none(self, file_path): - """Assert that the dispatched checker is None.""" - checker = self.dispatch(file_path) - self.assertTrue(checker is None, 'Checking: "%s"' % file_path) - - def assert_checker(self, file_path, expected_class): - """Assert the type of the dispatched checker.""" - checker = self.dispatch(file_path) - got_class = checker.__class__ - self.assertEquals(got_class, expected_class, - 'For path "%(file_path)s" got %(got_class)s when ' - "expecting %(expected_class)s." - % {"file_path": file_path, - "got_class": got_class, - "expected_class": expected_class}) - - def assert_checker_cpp(self, file_path): - """Assert that the dispatched checker is a CppChecker.""" - self.assert_checker(file_path, CppChecker) - - def assert_checker_python(self, file_path): - """Assert that the dispatched checker is a PythonChecker.""" - self.assert_checker(file_path, PythonChecker) - - def assert_checker_text(self, file_path): - """Assert that the dispatched checker is a TextChecker.""" - self.assert_checker(file_path, TextChecker) - - def test_cpp_paths(self): - """Test paths that should be checked as C++.""" - paths = [ - "-", - "foo.c", - "foo.cpp", - "foo.h", - ] - - for path in paths: - self.assert_checker_cpp(path) - - # Check checker attributes on a typical input. - file_base = "foo" - file_extension = "c" - file_path = file_base + "." + file_extension - self.assert_checker_cpp(file_path) - checker = self.dispatch(file_path) - self.assertEquals(checker.file_extension, file_extension) - self.assertEquals(checker.file_path, file_path) - self.assertEquals(checker.handle_style_error, self.mock_handle_style_error) - self.assertEquals(checker.min_confidence, 3) - # Check "-" for good measure. - file_base = "-" - file_extension = "" - file_path = file_base - self.assert_checker_cpp(file_path) - checker = self.dispatch(file_path) - self.assertEquals(checker.file_extension, file_extension) - self.assertEquals(checker.file_path, file_path) - - def test_python_paths(self): - """Test paths that should be checked as Python.""" - paths = [ - "foo.py", - "WebKitTools/Scripts/modules/text_style.py", - ] - - for path in paths: - self.assert_checker_python(path) - - # Check checker attributes on a typical input. - file_base = "foo" - file_extension = "css" - file_path = file_base + "." + file_extension - self.assert_checker_text(file_path) - checker = self.dispatch(file_path) - self.assertEquals(checker.file_path, file_path) - self.assertEquals(checker.handle_style_error, - self.mock_handle_style_error) - - def test_text_paths(self): - """Test paths that should be checked as text.""" - paths = [ - "ChangeLog", - "ChangeLog-2009-06-16", - "foo.ac", - "foo.cc", - "foo.cgi", - "foo.css", - "foo.exp", - "foo.flex", - "foo.gyp", - "foo.gypi", - "foo.html", - "foo.idl", - "foo.in", - "foo.js", - "foo.mm", - "foo.php", - "foo.pl", - "foo.pm", - "foo.pri", - "foo.pro", - "foo.rb", - "foo.sh", - "foo.txt", - "foo.wm", - "foo.xhtml", - "foo.y", - os.path.join("WebCore", "ChangeLog"), - os.path.join("WebCore", "inspector", "front-end", "inspector.js"), - os.path.join("WebKitTools", "Scripts", "check-webkit-style"), - ] - - for path in paths: - self.assert_checker_text(path) - - # Check checker attributes on a typical input. - file_base = "foo" - file_extension = "css" - file_path = file_base + "." + file_extension - self.assert_checker_text(file_path) - checker = self.dispatch(file_path) - self.assertEquals(checker.file_path, file_path) - self.assertEquals(checker.handle_style_error, self.mock_handle_style_error) - - def test_none_paths(self): - """Test paths that have no file type..""" - paths = [ - "Makefile", - "foo.asdf", # Non-sensical file extension. - "foo.png", - "foo.exe", - "foo.vcproj", - ] - - for path in paths: - self.assert_checker_none(path) - - -class StyleProcessorConfigurationTest(unittest.TestCase): - - """Tests the StyleProcessorConfiguration class.""" - - def setUp(self): - self._error_messages = [] - """The messages written to _mock_stderr_write() of this class.""" - - def _mock_stderr_write(self, message): - self._error_messages.append(message) - - def _style_checker_configuration(self, output_format="vs7"): - """Return a StyleProcessorConfiguration instance for testing.""" - base_rules = ["-whitespace", "+whitespace/tab"] - filter_configuration = FilterConfiguration(base_rules=base_rules) - - return StyleProcessorConfiguration( - filter_configuration=filter_configuration, - max_reports_per_category={"whitespace/newline": 1}, - min_confidence=3, - output_format=output_format, - stderr_write=self._mock_stderr_write) - - def test_init(self): - """Test the __init__() method.""" - configuration = self._style_checker_configuration() - - # Check that __init__ sets the "public" data attributes correctly. - self.assertEquals(configuration.max_reports_per_category, - {"whitespace/newline": 1}) - self.assertEquals(configuration.stderr_write, self._mock_stderr_write) - self.assertEquals(configuration.min_confidence, 3) - - def test_is_reportable(self): - """Test the is_reportable() method.""" - config = self._style_checker_configuration() - - self.assertTrue(config.is_reportable("whitespace/tab", 3, "foo.txt")) - - # Test the confidence check code path by varying the confidence. - self.assertFalse(config.is_reportable("whitespace/tab", 2, "foo.txt")) - - # Test the category check code path by varying the category. - self.assertFalse(config.is_reportable("whitespace/line", 4, "foo.txt")) - - def _call_write_style_error(self, output_format): - config = self._style_checker_configuration(output_format=output_format) - config.write_style_error(category="whitespace/tab", - confidence_in_error=5, - file_path="foo.h", - line_number=100, - message="message") - - def test_write_style_error_emacs(self): - """Test the write_style_error() method.""" - self._call_write_style_error("emacs") - self.assertEquals(self._error_messages, - ["foo.h:100: message [whitespace/tab] [5]\n"]) - - def test_write_style_error_vs7(self): - """Test the write_style_error() method.""" - self._call_write_style_error("vs7") - self.assertEquals(self._error_messages, - ["foo.h(100): message [whitespace/tab] [5]\n"]) - - -class StyleProcessor_EndToEndTest(LoggingTestCase): - - """Test the StyleProcessor class with an emphasis on end-to-end tests.""" - - def setUp(self): - LoggingTestCase.setUp(self) - self._messages = [] - - def _mock_stderr_write(self, message): - """Save a message so it can later be asserted.""" - self._messages.append(message) - - def test_init(self): - """Test __init__ constructor.""" - configuration = StyleProcessorConfiguration( - filter_configuration=FilterConfiguration(), - max_reports_per_category={}, - min_confidence=3, - output_format="vs7", - stderr_write=self._mock_stderr_write) - processor = StyleProcessor(configuration) - - self.assertEquals(processor.error_count, 0) - self.assertEquals(self._messages, []) - - def test_process(self): - configuration = StyleProcessorConfiguration( - filter_configuration=FilterConfiguration(), - max_reports_per_category={}, - min_confidence=3, - output_format="vs7", - stderr_write=self._mock_stderr_write) - processor = StyleProcessor(configuration) - - processor.process(lines=['line1', 'Line with tab:\t'], - file_path='foo.txt') - self.assertEquals(processor.error_count, 1) - expected_messages = ['foo.txt(2): Line contains tab character. ' - '[whitespace/tab] [5]\n'] - self.assertEquals(self._messages, expected_messages) - - -class StyleProcessor_CodeCoverageTest(LoggingTestCase): - - """Test the StyleProcessor class with an emphasis on code coverage. - - This class makes heavy use of mock objects. - - """ - - class MockDispatchedChecker(object): - - """A mock checker dispatched by the MockDispatcher.""" - - def __init__(self, file_path, min_confidence, style_error_handler): - self.file_path = file_path - self.min_confidence = min_confidence - self.style_error_handler = style_error_handler - - def check(self, lines): - self.lines = lines - - class MockDispatcher(object): - - """A mock CheckerDispatcher class.""" - - def __init__(self): - self.dispatched_checker = None - - def should_skip_with_warning(self, file_path): - return file_path.endswith('skip_with_warning.txt') - - def should_skip_without_warning(self, file_path): - return file_path.endswith('skip_without_warning.txt') - - def dispatch(self, file_path, style_error_handler, min_confidence): - if file_path.endswith('do_not_process.txt'): - return None - - checker = StyleProcessor_CodeCoverageTest.MockDispatchedChecker( - file_path, - min_confidence, - style_error_handler) - - # Save the dispatched checker so the current test case has a - # way to access and check it. - self.dispatched_checker = checker - - return checker - - def setUp(self): - LoggingTestCase.setUp(self) - # We can pass an error-message swallower here because error message - # output is tested instead in the end-to-end test case above. - configuration = StyleProcessorConfiguration( - filter_configuration=FilterConfiguration(), - max_reports_per_category={"whitespace/newline": 1}, - min_confidence=3, - output_format="vs7", - stderr_write=self._swallow_stderr_message) - - mock_carriage_checker_class = self._create_carriage_checker_class() - mock_dispatcher = self.MockDispatcher() - # We do not need to use a real incrementer here because error-count - # incrementing is tested instead in the end-to-end test case above. - mock_increment_error_count = self._do_nothing - - processor = StyleProcessor(configuration=configuration, - mock_carriage_checker_class=mock_carriage_checker_class, - mock_dispatcher=mock_dispatcher, - mock_increment_error_count=mock_increment_error_count) - - self._configuration = configuration - self._mock_dispatcher = mock_dispatcher - self._processor = processor - - def _do_nothing(self): - # We provide this function so the caller can pass it to the - # StyleProcessor constructor. This lets us assert the equality of - # the DefaultStyleErrorHandler instance generated by the process() - # method with an expected instance. - pass - - def _swallow_stderr_message(self, message): - """Swallow a message passed to stderr.write().""" - # This is a mock stderr.write() for passing to the constructor - # of the StyleProcessorConfiguration class. - pass - - def _create_carriage_checker_class(self): - - # Create a reference to self with a new name so its name does not - # conflict with the self introduced below. - test_case = self - - class MockCarriageChecker(object): - - """A mock carriage-return checker.""" - - def __init__(self, style_error_handler): - self.style_error_handler = style_error_handler - - # This gives the current test case access to the - # instantiated carriage checker. - test_case.carriage_checker = self - - def check(self, lines): - # Save the lines so the current test case has a way to access - # and check them. - self.lines = lines - - return lines - - return MockCarriageChecker - - def test_should_process__skip_without_warning(self): - """Test should_process() for a skip-without-warning file.""" - file_path = "foo/skip_without_warning.txt" - - self.assertFalse(self._processor.should_process(file_path)) - - def test_should_process__skip_with_warning(self): - """Test should_process() for a skip-with-warning file.""" - file_path = "foo/skip_with_warning.txt" - - self.assertFalse(self._processor.should_process(file_path)) - - self.assertLog(['WARNING: File exempt from style guide. ' - 'Skipping: "foo/skip_with_warning.txt"\n']) - - def test_should_process__true_result(self): - """Test should_process() for a file that should be processed.""" - file_path = "foo/skip_process.txt" - - self.assertTrue(self._processor.should_process(file_path)) - - def test_process__checker_dispatched(self): - """Test the process() method for a path with a dispatched checker.""" - file_path = 'foo.txt' - lines = ['line1', 'line2'] - line_numbers = [100] - - expected_error_handler = DefaultStyleErrorHandler( - configuration=self._configuration, - file_path=file_path, - increment_error_count=self._do_nothing, - line_numbers=line_numbers) - - self._processor.process(lines=lines, - file_path=file_path, - line_numbers=line_numbers) - - # Check that the carriage-return checker was instantiated correctly - # and was passed lines correctly. - carriage_checker = self.carriage_checker - self.assertEquals(carriage_checker.style_error_handler, - expected_error_handler) - self.assertEquals(carriage_checker.lines, ['line1', 'line2']) - - # Check that the style checker was dispatched correctly and was - # passed lines correctly. - checker = self._mock_dispatcher.dispatched_checker - self.assertEquals(checker.file_path, 'foo.txt') - self.assertEquals(checker.min_confidence, 3) - self.assertEquals(checker.style_error_handler, expected_error_handler) - - self.assertEquals(checker.lines, ['line1', 'line2']) - - def test_process__no_checker_dispatched(self): - """Test the process() method for a path with no dispatched checker.""" - path = os.path.join('foo', 'do_not_process.txt') - self.assertRaises(AssertionError, self._processor.process, - lines=['line1', 'line2'], file_path=path, - line_numbers=[100]) diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/__init__.py b/WebKitTools/Scripts/webkitpy/style/checkers/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/common.py b/WebKitTools/Scripts/webkitpy/style/checkers/common.py deleted file mode 100644 index 76aa956..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/common.py +++ /dev/null @@ -1,74 +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 style checking not specific to any one file type.""" - - -# FIXME: Test this list in the same way that the list of CppChecker -# categories is tested, for example by checking that all of its -# elements appear in the unit tests. This should probably be done -# after moving the relevant cpp_unittest.ErrorCollector code -# into a shared location and refactoring appropriately. -categories = set([ - "whitespace/carriage_return", - "whitespace/tab"]) - - -class CarriageReturnChecker(object): - - """Supports checking for and handling carriage returns.""" - - def __init__(self, handle_style_error): - self._handle_style_error = handle_style_error - - def check(self, lines): - """Check for and strip trailing carriage returns from lines.""" - for line_number in range(len(lines)): - if not lines[line_number].endswith("\r"): - continue - - self._handle_style_error(line_number + 1, # Correct for offset. - "whitespace/carriage_return", - 1, - "One or more unexpected \\r (^M) found; " - "better to use only a \\n") - - lines[line_number] = lines[line_number].rstrip("\r") - - return lines - - -class TabChecker(object): - - """Supports checking for and handling tabs.""" - - def __init__(self, file_path, handle_style_error): - self.file_path = file_path - self.handle_style_error = handle_style_error - - def check(self, lines): - # FIXME: share with cpp_style. - for line_number, line in enumerate(lines): - if "\t" in line: - self.handle_style_error(line_number + 1, - "whitespace/tab", 5, - "Line contains tab character.") diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py b/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py deleted file mode 100644 index 1fe1263..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py +++ /dev/null @@ -1,124 +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 common.py.""" - -import unittest - -from common import CarriageReturnChecker -from common import TabChecker - -# FIXME: The unit tests for the cpp, text, and common checkers should -# share supporting test code. This can include, for example, the -# mock style error handling code and the code to check that all -# of a checker's categories are covered by the unit tests. -# Such shared code can be located in a shared test file, perhaps -# even this file. -class CarriageReturnCheckerTest(unittest.TestCase): - - """Tests check_no_carriage_return().""" - - _category = "whitespace/carriage_return" - _confidence = 1 - _expected_message = ("One or more unexpected \\r (^M) found; " - "better to use only a \\n") - - def setUp(self): - self._style_errors = [] # The list of accumulated style errors. - - def _mock_style_error_handler(self, line_number, category, confidence, - message): - """Append the error information to the list of style errors.""" - error = (line_number, category, confidence, message) - self._style_errors.append(error) - - def assert_carriage_return(self, input_lines, expected_lines, error_lines): - """Process the given line and assert that the result is correct.""" - handle_style_error = self._mock_style_error_handler - - checker = CarriageReturnChecker(handle_style_error) - output_lines = checker.check(input_lines) - - # Check both the return value and error messages. - self.assertEquals(output_lines, expected_lines) - - expected_errors = [(line_number, self._category, self._confidence, - self._expected_message) - for line_number in error_lines] - self.assertEquals(self._style_errors, expected_errors) - - def test_ends_with_carriage(self): - self.assert_carriage_return(["carriage return\r"], - ["carriage return"], - [1]) - - def test_ends_with_nothing(self): - self.assert_carriage_return(["no carriage return"], - ["no carriage return"], - []) - - def test_ends_with_newline(self): - self.assert_carriage_return(["no carriage return\n"], - ["no carriage return\n"], - []) - - def test_carriage_in_middle(self): - # The CarriageReturnChecker checks only the final character - # of each line. - self.assert_carriage_return(["carriage\r in a string"], - ["carriage\r in a string"], - []) - - def test_multiple_errors(self): - self.assert_carriage_return(["line1", "line2\r", "line3\r"], - ["line1", "line2", "line3"], - [2, 3]) - - -class TabCheckerTest(unittest.TestCase): - - """Tests for TabChecker.""" - - def assert_tab(self, input_lines, error_lines): - """Assert when the given lines contain tabs.""" - self._error_lines = [] - - def style_error_handler(line_number, category, confidence, message): - self.assertEqual(category, 'whitespace/tab') - self.assertEqual(confidence, 5) - self.assertEqual(message, 'Line contains tab character.') - self._error_lines.append(line_number) - - checker = TabChecker('', style_error_handler) - checker.check(input_lines) - self.assertEquals(self._error_lines, error_lines) - - def test_notab(self): - self.assert_tab([''], []) - self.assert_tab(['foo', 'bar'], []) - - def test_tab(self): - self.assert_tab(['\tfoo'], [1]) - self.assert_tab(['line1', '\tline2', 'line3\t'], [2, 3]) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/cpp.py b/WebKitTools/Scripts/webkitpy/style/checkers/cpp.py deleted file mode 100644 index 590bba9..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/cpp.py +++ /dev/null @@ -1,3126 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2009 Google Inc. All rights reserved. -# Copyright (C) 2009 Torch Mobile Inc. -# Copyright (C) 2009 Apple Inc. 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 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. - -# This is the modified version of Google's cpplint. The original code is -# http://google-styleguide.googlecode.com/svn/trunk/cpplint/cpplint.py - -"""Support for check-webkit-style.""" - -import codecs -import math # for log -import os -import os.path -import re -import sre_compile -import string -import sys -import unicodedata - - -# Headers that we consider STL headers. -_STL_HEADERS = frozenset([ - 'algobase.h', 'algorithm', 'alloc.h', 'bitset', 'deque', 'exception', - 'function.h', 'functional', 'hash_map', 'hash_map.h', 'hash_set', - 'hash_set.h', 'iterator', 'list', 'list.h', 'map', 'memory', 'pair.h', - 'pthread_alloc', 'queue', 'set', 'set.h', 'sstream', 'stack', - 'stl_alloc.h', 'stl_relops.h', 'type_traits.h', - 'utility', 'vector', 'vector.h', - ]) - - -# Non-STL C++ system headers. -_CPP_HEADERS = frozenset([ - 'algo.h', 'builtinbuf.h', 'bvector.h', 'cassert', 'cctype', - 'cerrno', 'cfloat', 'ciso646', 'climits', 'clocale', 'cmath', - 'complex', 'complex.h', 'csetjmp', 'csignal', 'cstdarg', 'cstddef', - 'cstdio', 'cstdlib', 'cstring', 'ctime', 'cwchar', 'cwctype', - 'defalloc.h', 'deque.h', 'editbuf.h', 'exception', 'fstream', - 'fstream.h', 'hashtable.h', 'heap.h', 'indstream.h', 'iomanip', - 'iomanip.h', 'ios', 'iosfwd', 'iostream', 'iostream.h', 'istream.h', - 'iterator.h', 'limits', 'map.h', 'multimap.h', 'multiset.h', - 'numeric', 'ostream.h', 'parsestream.h', 'pfstream.h', 'PlotFile.h', - 'procbuf.h', 'pthread_alloc.h', 'rope', 'rope.h', 'ropeimpl.h', - 'SFile.h', 'slist', 'slist.h', 'stack.h', 'stdexcept', - 'stdiostream.h', 'streambuf.h', 'stream.h', 'strfile.h', 'string', - 'strstream', 'strstream.h', 'tempbuf.h', 'tree.h', 'typeinfo', 'valarray', - ]) - - -# Assertion macros. These are defined in base/logging.h and -# testing/base/gunit.h. Note that the _M versions need to come first -# for substring matching to work. -_CHECK_MACROS = [ - 'DCHECK', 'CHECK', - 'EXPECT_TRUE_M', 'EXPECT_TRUE', - 'ASSERT_TRUE_M', 'ASSERT_TRUE', - 'EXPECT_FALSE_M', 'EXPECT_FALSE', - 'ASSERT_FALSE_M', 'ASSERT_FALSE', - ] - -# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE -_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS]) - -for op, replacement in [('==', 'EQ'), ('!=', 'NE'), - ('>=', 'GE'), ('>', 'GT'), - ('<=', 'LE'), ('<', 'LT')]: - _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement - _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement - _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement - _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement - _CHECK_REPLACEMENT['EXPECT_TRUE_M'][op] = 'EXPECT_%s_M' % replacement - _CHECK_REPLACEMENT['ASSERT_TRUE_M'][op] = 'ASSERT_%s_M' % replacement - -for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'), - ('>=', 'LT'), ('>', 'LE'), - ('<=', 'GT'), ('<', 'GE')]: - _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement - _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement - _CHECK_REPLACEMENT['EXPECT_FALSE_M'][op] = 'EXPECT_%s_M' % inv_replacement - _CHECK_REPLACEMENT['ASSERT_FALSE_M'][op] = 'ASSERT_%s_M' % inv_replacement - - -# These constants define types of headers for use with -# _IncludeState.check_next_include_order(). -_CONFIG_HEADER = 0 -_PRIMARY_HEADER = 1 -_OTHER_HEADER = 2 -_MOC_HEADER = 3 - - -# The regexp compilation caching is inlined in all regexp functions for -# performance reasons; factoring it out into a separate function turns out -# to be noticeably expensive. -_regexp_compile_cache = {} - - -def match(pattern, s): - """Matches the string with the pattern, caching the compiled regexp.""" - if not pattern in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) - return _regexp_compile_cache[pattern].match(s) - - -def search(pattern, s): - """Searches the string for the pattern, caching the compiled regexp.""" - if not pattern in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) - return _regexp_compile_cache[pattern].search(s) - - -def sub(pattern, replacement, s): - """Substitutes occurrences of a pattern, caching the compiled regexp.""" - if not pattern in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) - return _regexp_compile_cache[pattern].sub(replacement, s) - - -def subn(pattern, replacement, s): - """Substitutes occurrences of a pattern, caching the compiled regexp.""" - if not pattern in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) - return _regexp_compile_cache[pattern].subn(replacement, s) - - -def iteratively_replace_matches_with_char(pattern, char_replacement, s): - """Returns the string with replacement done. - - Every character in the match is replaced with char. - Due to the iterative nature, pattern should not match char or - there will be an infinite loop. - - Example: - pattern = r'<[^>]>' # template parameters - char_replacement = '_' - s = 'A<B<C, D>>' - Returns 'A_________' - - Args: - pattern: The regex to match. - char_replacement: The character to put in place of every - character of the match. - s: The string on which to do the replacements. - - Returns: - True, if the given line is blank. - """ - while True: - matched = search(pattern, s) - if not matched: - return s - start_match_index = matched.start(0) - end_match_index = matched.end(0) - match_length = end_match_index - start_match_index - s = s[:start_match_index] + char_replacement * match_length + s[end_match_index:] - - -def up_to_unmatched_closing_paren(s): - """Splits a string into two parts up to first unmatched ')'. - - Args: - s: a string which is a substring of line after '(' - (e.g., "a == (b + c))"). - - Returns: - A pair of strings (prefix before first unmatched ')', - remainder of s after first unmatched ')'), e.g., - up_to_unmatched_closing_paren("a == (b + c)) { ") - returns "a == (b + c)", " {". - Returns None, None if there is no unmatched ')' - - """ - i = 1 - for pos, c in enumerate(s): - if c == '(': - i += 1 - elif c == ')': - i -= 1 - if i == 0: - return s[:pos], s[pos + 1:] - return None, None - -class _IncludeState(dict): - """Tracks line numbers for includes, and the order in which includes appear. - - As a dict, an _IncludeState object serves as a mapping between include - filename and line number on which that file was included. - - Call check_next_include_order() once for each header in the file, passing - in the type constants defined above. Calls in an illegal order will - raise an _IncludeError with an appropriate error message. - - """ - # self._section will move monotonically through this set. If it ever - # needs to move backwards, check_next_include_order will raise an error. - _INITIAL_SECTION = 0 - _CONFIG_SECTION = 1 - _PRIMARY_SECTION = 2 - _OTHER_SECTION = 3 - - _TYPE_NAMES = { - _CONFIG_HEADER: 'WebCore config.h', - _PRIMARY_HEADER: 'header this file implements', - _OTHER_HEADER: 'other header', - _MOC_HEADER: 'moc file', - } - _SECTION_NAMES = { - _INITIAL_SECTION: "... nothing.", - _CONFIG_SECTION: "WebCore config.h.", - _PRIMARY_SECTION: 'a header this file implements.', - _OTHER_SECTION: 'other header.', - } - - def __init__(self): - dict.__init__(self) - self._section = self._INITIAL_SECTION - self._visited_primary_section = False - self.header_types = dict(); - - def visited_primary_section(self): - return self._visited_primary_section - - def check_next_include_order(self, header_type, file_is_header): - """Returns a non-empty error message if the next header is out of order. - - This function also updates the internal state to be ready to check - the next include. - - Args: - header_type: One of the _XXX_HEADER constants defined above. - file_is_header: Whether the file that owns this _IncludeState is itself a header - - Returns: - The empty string if the header is in the right order, or an - error message describing what's wrong. - - """ - if header_type == _CONFIG_HEADER and file_is_header: - return 'Header file should not contain WebCore config.h.' - if header_type == _PRIMARY_HEADER and file_is_header: - return 'Header file should not contain itself.' - if header_type == _MOC_HEADER: - return '' - - error_message = '' - if self._section != self._OTHER_SECTION: - before_error_message = ('Found %s before %s' % - (self._TYPE_NAMES[header_type], - self._SECTION_NAMES[self._section + 1])) - after_error_message = ('Found %s after %s' % - (self._TYPE_NAMES[header_type], - self._SECTION_NAMES[self._section])) - - if header_type == _CONFIG_HEADER: - if self._section >= self._CONFIG_SECTION: - error_message = after_error_message - self._section = self._CONFIG_SECTION - elif header_type == _PRIMARY_HEADER: - if self._section >= self._PRIMARY_SECTION: - error_message = after_error_message - elif self._section < self._CONFIG_SECTION: - error_message = before_error_message - self._section = self._PRIMARY_SECTION - self._visited_primary_section = True - else: - assert header_type == _OTHER_HEADER - if not file_is_header and self._section < self._PRIMARY_SECTION: - error_message = before_error_message - self._section = self._OTHER_SECTION - - return error_message - - -class _FunctionState(object): - """Tracks current function name and the number of lines in its body. - - Attributes: - min_confidence: The minimum confidence level to use while checking style. - - """ - - _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc. - _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER. - - def __init__(self, min_confidence): - self.min_confidence = min_confidence - self.current_function = '' - self.in_a_function = False - self.lines_in_function = 0 - # Make sure these will not be mistaken for real lines (even when a - # small amount is added to them). - self.body_start_line_number = -1000 - self.ending_line_number = -1000 - - def begin(self, function_name, body_start_line_number, ending_line_number): - """Start analyzing function body. - - Args: - function_name: The name of the function being tracked. - ending_line_number: The line number where the function ends. - """ - self.in_a_function = True - self.lines_in_function = 0 - self.current_function = function_name - self.body_start_line_number = body_start_line_number - self.ending_line_number = ending_line_number - - def count(self, line_number): - """Count line in current function body.""" - if self.in_a_function and line_number >= self.body_start_line_number: - self.lines_in_function += 1 - - def check(self, error, line_number): - """Report if too many lines in function body. - - Args: - error: The function to call with any errors found. - line_number: The number of the line to check. - """ - if match(r'T(EST|est)', self.current_function): - base_trigger = self._TEST_TRIGGER - else: - base_trigger = self._NORMAL_TRIGGER - trigger = base_trigger * 2 ** self.min_confidence - - if self.lines_in_function > trigger: - error_level = int(math.log(self.lines_in_function / base_trigger, 2)) - # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ... - if error_level > 5: - error_level = 5 - error(line_number, 'readability/fn_size', error_level, - 'Small and focused functions are preferred:' - ' %s has %d non-comment lines' - ' (error triggered by exceeding %d lines).' % ( - self.current_function, self.lines_in_function, trigger)) - - def end(self): - """Stop analyzing function body.""" - self.in_a_function = False - - -class _IncludeError(Exception): - """Indicates a problem with the include order in a file.""" - pass - - -def is_c_or_objective_c(file_extension): - """Return whether the file extension corresponds to C or Objective-C. - - Args: - file_extension: The file extension without the leading dot. - - """ - return file_extension in ['c', 'm'] - - -class FileInfo: - """Provides utility functions for filenames. - - FileInfo provides easy access to the components of a file's path - relative to the project root. - """ - - def __init__(self, filename): - self._filename = filename - - def full_name(self): - """Make Windows paths like Unix.""" - return os.path.abspath(self._filename).replace('\\', '/') - - def repository_name(self): - """Full name after removing the local path to the repository. - - If we have a real absolute path name here we can try to do something smart: - detecting the root of the checkout and truncating /path/to/checkout from - the name so that we get header guards that don't include things like - "C:\Documents and Settings\..." or "/home/username/..." in them and thus - people on different computers who have checked the source out to different - locations won't see bogus errors. - """ - fullname = self.full_name() - - if os.path.exists(fullname): - project_dir = os.path.dirname(fullname) - - if os.path.exists(os.path.join(project_dir, ".svn")): - # If there's a .svn file in the current directory, we - # recursively look up the directory tree for the top - # of the SVN checkout - root_dir = project_dir - one_up_dir = os.path.dirname(root_dir) - while os.path.exists(os.path.join(one_up_dir, ".svn")): - root_dir = os.path.dirname(root_dir) - one_up_dir = os.path.dirname(one_up_dir) - - prefix = os.path.commonprefix([root_dir, project_dir]) - return fullname[len(prefix) + 1:] - - # Not SVN? Try to find a git top level directory by - # searching up from the current path. - root_dir = os.path.dirname(fullname) - while (root_dir != os.path.dirname(root_dir) - and not os.path.exists(os.path.join(root_dir, ".git"))): - root_dir = os.path.dirname(root_dir) - if os.path.exists(os.path.join(root_dir, ".git")): - prefix = os.path.commonprefix([root_dir, project_dir]) - return fullname[len(prefix) + 1:] - - # Don't know what to do; header guard warnings may be wrong... - return fullname - - def split(self): - """Splits the file into the directory, basename, and extension. - - For 'chrome/browser/browser.cpp', Split() would - return ('chrome/browser', 'browser', '.cpp') - - Returns: - A tuple of (directory, basename, extension). - """ - - googlename = self.repository_name() - project, rest = os.path.split(googlename) - return (project,) + os.path.splitext(rest) - - def base_name(self): - """File base name - text after the final slash, before the final period.""" - return self.split()[1] - - def extension(self): - """File extension - text following the final period.""" - return self.split()[2] - - def no_extension(self): - """File has no source file extension.""" - return '/'.join(self.split()[0:2]) - - def is_source(self): - """File has a source file extension.""" - return self.extension()[1:] in ('c', 'cc', 'cpp', 'cxx') - - -# Matches standard C++ escape esequences per 2.13.2.3 of the C++ standard. -_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile( - r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)') -# Matches strings. Escape codes should already be removed by ESCAPES. -_RE_PATTERN_CLEANSE_LINE_DOUBLE_QUOTES = re.compile(r'"[^"]*"') -# Matches characters. Escape codes should already be removed by ESCAPES. -_RE_PATTERN_CLEANSE_LINE_SINGLE_QUOTES = re.compile(r"'.'") -# Matches multi-line C++ comments. -# This RE is a little bit more complicated than one might expect, because we -# have to take care of space removals tools so we can handle comments inside -# statements better. -# The current rule is: We only clear spaces from both sides when we're at the -# end of the line. Otherwise, we try to remove spaces from the right side, -# if this doesn't work we try on left side but only if there's a non-character -# on the right. -_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile( - r"""(\s*/\*.*\*/\s*$| - /\*.*\*/\s+| - \s+/\*.*\*/(?=\W)| - /\*.*\*/)""", re.VERBOSE) - - -def is_cpp_string(line): - """Does line terminate so, that the next symbol is in string constant. - - This function does not consider single-line nor multi-line comments. - - Args: - line: is a partial line of code starting from the 0..n. - - Returns: - True, if next character appended to 'line' is inside a - string constant. - """ - - line = line.replace(r'\\', 'XX') # after this, \\" does not match to \" - return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1 - - -def find_next_multi_line_comment_start(lines, line_index): - """Find the beginning marker for a multiline comment.""" - while line_index < len(lines): - if lines[line_index].strip().startswith('/*'): - # Only return this marker if the comment goes beyond this line - if lines[line_index].strip().find('*/', 2) < 0: - return line_index - line_index += 1 - return len(lines) - - -def find_next_multi_line_comment_end(lines, line_index): - """We are inside a comment, find the end marker.""" - while line_index < len(lines): - if lines[line_index].strip().endswith('*/'): - return line_index - line_index += 1 - return len(lines) - - -def remove_multi_line_comments_from_range(lines, begin, end): - """Clears a range of lines for multi-line comments.""" - # Having // dummy comments makes the lines non-empty, so we will not get - # unnecessary blank line warnings later in the code. - for i in range(begin, end): - lines[i] = '// dummy' - - -def remove_multi_line_comments(lines, error): - """Removes multiline (c-style) comments from lines.""" - line_index = 0 - while line_index < len(lines): - line_index_begin = find_next_multi_line_comment_start(lines, line_index) - if line_index_begin >= len(lines): - return - line_index_end = find_next_multi_line_comment_end(lines, line_index_begin) - if line_index_end >= len(lines): - error(line_index_begin + 1, 'readability/multiline_comment', 5, - 'Could not find end of multi-line comment') - return - remove_multi_line_comments_from_range(lines, line_index_begin, line_index_end + 1) - line_index = line_index_end + 1 - - -def cleanse_comments(line): - """Removes //-comments and single-line C-style /* */ comments. - - Args: - line: A line of C++ source. - - Returns: - The line with single-line comments removed. - """ - comment_position = line.find('//') - if comment_position != -1 and not is_cpp_string(line[:comment_position]): - line = line[:comment_position] - # get rid of /* ... */ - return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line) - - -class CleansedLines(object): - """Holds 3 copies of all lines with different preprocessing applied to them. - - 1) elided member contains lines without strings and comments, - 2) lines member contains lines without comments, and - 3) raw member contains all the lines without processing. - All these three members are of <type 'list'>, and of the same length. - """ - - def __init__(self, lines): - self.elided = [] - self.lines = [] - self.raw_lines = lines - self._num_lines = len(lines) - for line_number in range(len(lines)): - self.lines.append(cleanse_comments(lines[line_number])) - elided = self.collapse_strings(lines[line_number]) - self.elided.append(cleanse_comments(elided)) - - def num_lines(self): - """Returns the number of lines represented.""" - return self._num_lines - - @staticmethod - def collapse_strings(elided): - """Collapses strings and chars on a line to simple "" or '' blocks. - - We nix strings first so we're not fooled by text like '"http://"' - - Args: - elided: The line being processed. - - Returns: - The line with collapsed strings. - """ - if not _RE_PATTERN_INCLUDE.match(elided): - # Remove escaped characters first to make quote/single quote collapsing - # basic. Things that look like escaped characters shouldn't occur - # outside of strings and chars. - elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided) - elided = _RE_PATTERN_CLEANSE_LINE_SINGLE_QUOTES.sub("''", elided) - elided = _RE_PATTERN_CLEANSE_LINE_DOUBLE_QUOTES.sub('""', elided) - return elided - - -def close_expression(clean_lines, line_number, pos): - """If input points to ( or { or [, finds the position that closes it. - - If clean_lines.elided[line_number][pos] points to a '(' or '{' or '[', finds - the line_number/pos that correspond to the closing of the expression. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - pos: A position on the line. - - Returns: - A tuple (line, line_number, pos) pointer *past* the closing brace, or - ('', len(clean_lines.elided), -1) if we never find a close. Note we - ignore strings and comments when matching; and the line we return is the - 'cleansed' line at line_number. - """ - - line = clean_lines.elided[line_number] - start_character = line[pos] - if start_character not in '({[': - return (line, clean_lines.num_lines(), -1) - if start_character == '(': - end_character = ')' - if start_character == '[': - end_character = ']' - if start_character == '{': - end_character = '}' - - num_open = line.count(start_character) - line.count(end_character) - while num_open > 0: - line_number += 1 - if line_number >= clean_lines.num_lines(): - return ('', len(clean_lines.elided), -1) - line = clean_lines.elided[line_number] - num_open += line.count(start_character) - line.count(end_character) - # OK, now find the end_character that actually got us back to even - endpos = len(line) - while num_open >= 0: - endpos = line.rfind(')', 0, endpos) - num_open -= 1 # chopped off another ) - return (line, line_number, endpos + 1) - - -def check_for_copyright(lines, error): - """Logs an error if no Copyright message appears at the top of the file.""" - - # We'll say it should occur by line 10. Don't forget there's a - # dummy line at the front. - for line in xrange(1, min(len(lines), 11)): - if re.search(r'Copyright', lines[line], re.I): - break - else: # means no copyright line was found - error(0, 'legal/copyright', 5, - 'No copyright message found. ' - 'You should have a line: "Copyright [year] <Copyright Owner>"') - - -def get_header_guard_cpp_variable(filename): - """Returns the CPP variable that should be used as a header guard. - - Args: - filename: The name of a C++ header file. - - Returns: - The CPP variable that should be used as a header guard in the - named file. - - """ - - # Restores original filename in case that style checker is invoked from Emacs's - # flymake. - filename = re.sub(r'_flymake\.h$', '.h', filename) - - return sub(r'[-.\s]', '_', os.path.basename(filename)) - - -def check_for_header_guard(filename, lines, error): - """Checks that the file contains a header guard. - - Logs an error if no #ifndef header guard is present. For other - headers, checks that the full pathname is used. - - Args: - filename: The name of the C++ header file. - lines: An array of strings, each representing a line of the file. - error: The function to call with any errors found. - """ - - cppvar = get_header_guard_cpp_variable(filename) - - ifndef = None - ifndef_line_number = 0 - define = None - for line_number, line in enumerate(lines): - line_split = line.split() - if len(line_split) >= 2: - # find the first occurrence of #ifndef and #define, save arg - if not ifndef and line_split[0] == '#ifndef': - # set ifndef to the header guard presented on the #ifndef line. - ifndef = line_split[1] - ifndef_line_number = line_number - if not define and line_split[0] == '#define': - define = line_split[1] - if define and ifndef: - break - - if not ifndef or not define or ifndef != define: - error(0, 'build/header_guard', 5, - 'No #ifndef header guard found, suggested CPP variable is: %s' % - cppvar) - return - - # The guard should be File_h. - if ifndef != cppvar: - error(ifndef_line_number, 'build/header_guard', 5, - '#ifndef header guard has wrong style, please use: %s' % cppvar) - - -def check_for_unicode_replacement_characters(lines, error): - """Logs an error for each line containing Unicode replacement characters. - - These indicate that either the file contained invalid UTF-8 (likely) - or Unicode replacement characters (which it shouldn't). Note that - it's possible for this to throw off line numbering if the invalid - UTF-8 occurred adjacent to a newline. - - Args: - lines: An array of strings, each representing a line of the file. - error: The function to call with any errors found. - """ - for line_number, line in enumerate(lines): - if u'\ufffd' in line: - error(line_number, 'readability/utf8', 5, - 'Line contains invalid UTF-8 (or Unicode replacement character).') - - -def check_for_new_line_at_eof(lines, error): - """Logs an error if there is no newline char at the end of the file. - - Args: - lines: An array of strings, each representing a line of the file. - error: The function to call with any errors found. - """ - - # The array lines() was created by adding two newlines to the - # original file (go figure), then splitting on \n. - # To verify that the file ends in \n, we just have to make sure the - # last-but-two element of lines() exists and is empty. - if len(lines) < 3 or lines[-2]: - error(len(lines) - 2, 'whitespace/ending_newline', 5, - 'Could not find a newline character at the end of the file.') - - -def check_for_multiline_comments_and_strings(clean_lines, line_number, error): - """Logs an error if we see /* ... */ or "..." that extend past one line. - - /* ... */ comments are legit inside macros, for one line. - Otherwise, we prefer // comments, so it's ok to warn about the - other. Likewise, it's ok for strings to extend across multiple - lines, as long as a line continuation character (backslash) - terminates each line. Although not currently prohibited by the C++ - style guide, it's ugly and unnecessary. We don't do well with either - in this lint program, so we warn about both. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[line_number] - - # Remove all \\ (escaped backslashes) from the line. They are OK, and the - # second (escaped) slash may trigger later \" detection erroneously. - line = line.replace('\\\\', '') - - if line.count('/*') > line.count('*/'): - error(line_number, 'readability/multiline_comment', 5, - 'Complex multi-line /*...*/-style comment found. ' - 'Lint may give bogus warnings. ' - 'Consider replacing these with //-style comments, ' - 'with #if 0...#endif, ' - 'or with more clearly structured multi-line comments.') - - if (line.count('"') - line.count('\\"')) % 2: - error(line_number, 'readability/multiline_string', 5, - 'Multi-line string ("...") found. This lint script doesn\'t ' - 'do well with such strings, and may give bogus warnings. They\'re ' - 'ugly and unnecessary, and you should use concatenation instead".') - - -_THREADING_LIST = ( - ('asctime(', 'asctime_r('), - ('ctime(', 'ctime_r('), - ('getgrgid(', 'getgrgid_r('), - ('getgrnam(', 'getgrnam_r('), - ('getlogin(', 'getlogin_r('), - ('getpwnam(', 'getpwnam_r('), - ('getpwuid(', 'getpwuid_r('), - ('gmtime(', 'gmtime_r('), - ('localtime(', 'localtime_r('), - ('rand(', 'rand_r('), - ('readdir(', 'readdir_r('), - ('strtok(', 'strtok_r('), - ('ttyname(', 'ttyname_r('), - ) - - -def check_posix_threading(clean_lines, line_number, error): - """Checks for calls to thread-unsafe functions. - - Much code has been originally written without consideration of - multi-threading. Also, engineers are relying on their old experience; - they have learned posix before threading extensions were added. These - tests guide the engineers to use thread-safe functions (when using - posix directly). - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[line_number] - for single_thread_function, multithread_safe_function in _THREADING_LIST: - index = line.find(single_thread_function) - # Comparisons made explicit for clarity -- pylint: disable-msg=C6403 - if index >= 0 and (index == 0 or (not line[index - 1].isalnum() - and line[index - 1] not in ('_', '.', '>'))): - error(line_number, 'runtime/threadsafe_fn', 2, - 'Consider using ' + multithread_safe_function + - '...) instead of ' + single_thread_function + - '...) for improved thread safety.') - - -# Matches invalid increment: *count++, which moves pointer instead of -# incrementing a value. -_RE_PATTERN_INVALID_INCREMENT = re.compile( - r'^\s*\*\w+(\+\+|--);') - - -def check_invalid_increment(clean_lines, line_number, error): - """Checks for invalid increment *count++. - - For example following function: - void increment_counter(int* count) { - *count++; - } - is invalid, because it effectively does count++, moving pointer, and should - be replaced with ++*count, (*count)++ or *count += 1. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[line_number] - if _RE_PATTERN_INVALID_INCREMENT.match(line): - error(line_number, 'runtime/invalid_increment', 5, - 'Changing pointer instead of value (or unused value of operator*).') - - -class _ClassInfo(object): - """Stores information about a class.""" - - def __init__(self, name, line_number): - self.name = name - self.line_number = line_number - self.seen_open_brace = False - self.is_derived = False - self.virtual_method_line_number = None - self.has_virtual_destructor = False - self.brace_depth = 0 - - -class _ClassState(object): - """Holds the current state of the parse relating to class declarations. - - It maintains a stack of _ClassInfos representing the parser's guess - as to the current nesting of class declarations. The innermost class - is at the top (back) of the stack. Typically, the stack will either - be empty or have exactly one entry. - """ - - def __init__(self): - self.classinfo_stack = [] - - def check_finished(self, error): - """Checks that all classes have been completely parsed. - - Call this when all lines in a file have been processed. - Args: - error: The function to call with any errors found. - """ - if self.classinfo_stack: - # Note: This test can result in false positives if #ifdef constructs - # get in the way of brace matching. See the testBuildClass test in - # cpp_style_unittest.py for an example of this. - error(self.classinfo_stack[0].line_number, 'build/class', 5, - 'Failed to find complete declaration of class %s' % - self.classinfo_stack[0].name) - - -class _FileState(object): - def __init__(self): - self._did_inside_namespace_indent_warning = False - - def set_did_inside_namespace_indent_warning(self): - self._did_inside_namespace_indent_warning = True - - def did_inside_namespace_indent_warning(self): - return self._did_inside_namespace_indent_warning - -def check_for_non_standard_constructs(clean_lines, line_number, - class_state, error): - """Logs an error if we see certain non-ANSI constructs ignored by gcc-2. - - Complain about several constructs which gcc-2 accepts, but which are - not standard C++. Warning about these in lint is one way to ease the - transition to new compilers. - - put storage class first (e.g. "static const" instead of "const static"). - - "%lld" instead of %qd" in printf-type functions. - - "%1$d" is non-standard in printf-type functions. - - "\%" is an undefined character escape sequence. - - text after #endif is not allowed. - - invalid inner-style forward declaration. - - >? and <? operators, and their >?= and <?= cousins. - - classes with virtual methods need virtual destructors (compiler warning - available, but not turned on yet.) - - Additionally, check for constructor/destructor style violations as it - is very convenient to do so while checking for gcc-2 compliance. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - class_state: A _ClassState instance which maintains information about - the current stack of nested class declarations being parsed. - error: A callable to which errors are reported, which takes parameters: - line number, error level, and message - """ - - # Remove comments from the line, but leave in strings for now. - line = clean_lines.lines[line_number] - - if search(r'printf\s*\(.*".*%[-+ ]?\d*q', line): - error(line_number, 'runtime/printf_format', 3, - '%q in format strings is deprecated. Use %ll instead.') - - if search(r'printf\s*\(.*".*%\d+\$', line): - error(line_number, 'runtime/printf_format', 2, - '%N$ formats are unconventional. Try rewriting to avoid them.') - - # Remove escaped backslashes before looking for undefined escapes. - line = line.replace('\\\\', '') - - if search(r'("|\').*\\(%|\[|\(|{)', line): - error(line_number, 'build/printf_format', 3, - '%, [, (, and { are undefined character escapes. Unescape them.') - - # For the rest, work with both comments and strings removed. - line = clean_lines.elided[line_number] - - if search(r'\b(const|volatile|void|char|short|int|long' - r'|float|double|signed|unsigned' - r'|schar|u?int8|u?int16|u?int32|u?int64)' - r'\s+(auto|register|static|extern|typedef)\b', - line): - error(line_number, 'build/storage_class', 5, - 'Storage class (static, extern, typedef, etc) should be first.') - - if match(r'\s*#\s*endif\s*[^/\s]+', line): - error(line_number, 'build/endif_comment', 5, - 'Uncommented text after #endif is non-standard. Use a comment.') - - if match(r'\s*class\s+(\w+\s*::\s*)+\w+\s*;', line): - error(line_number, 'build/forward_decl', 5, - 'Inner-style forward declarations are invalid. Remove this line.') - - if search(r'(\w+|[+-]?\d+(\.\d*)?)\s*(<|>)\?=?\s*(\w+|[+-]?\d+)(\.\d*)?', line): - error(line_number, 'build/deprecated', 3, - '>? and <? (max and min) operators are non-standard and deprecated.') - - # Track class entry and exit, and attempt to find cases within the - # class declaration that don't meet the C++ style - # guidelines. Tracking is very dependent on the code matching Google - # style guidelines, but it seems to perform well enough in testing - # to be a worthwhile addition to the checks. - classinfo_stack = class_state.classinfo_stack - # Look for a class declaration - class_decl_match = match( - r'\s*(template\s*<[\w\s<>,:]*>\s*)?(class|struct)\s+(\w+(::\w+)*)', line) - if class_decl_match: - classinfo_stack.append(_ClassInfo(class_decl_match.group(3), line_number)) - - # Everything else in this function uses the top of the stack if it's - # not empty. - if not classinfo_stack: - return - - classinfo = classinfo_stack[-1] - - # If the opening brace hasn't been seen look for it and also - # parent class declarations. - if not classinfo.seen_open_brace: - # If the line has a ';' in it, assume it's a forward declaration or - # a single-line class declaration, which we won't process. - if line.find(';') != -1: - classinfo_stack.pop() - return - classinfo.seen_open_brace = (line.find('{') != -1) - # Look for a bare ':' - if search('(^|[^:]):($|[^:])', line): - classinfo.is_derived = True - if not classinfo.seen_open_brace: - return # Everything else in this function is for after open brace - - # The class may have been declared with namespace or classname qualifiers. - # The constructor and destructor will not have those qualifiers. - base_classname = classinfo.name.split('::')[-1] - - # Look for single-argument constructors that aren't marked explicit. - # Technically a valid construct, but against style. - args = match(r'(?<!explicit)\s+%s\s*\(([^,()]+)\)' - % re.escape(base_classname), - line) - if (args - and args.group(1) != 'void' - and not match(r'(const\s+)?%s\s*&' % re.escape(base_classname), - args.group(1).strip())): - error(line_number, 'runtime/explicit', 5, - 'Single-argument constructors should be marked explicit.') - - # Look for methods declared virtual. - if search(r'\bvirtual\b', line): - classinfo.virtual_method_line_number = line_number - # Only look for a destructor declaration on the same line. It would - # be extremely unlikely for the destructor declaration to occupy - # more than one line. - if search(r'~%s\s*\(' % base_classname, line): - classinfo.has_virtual_destructor = True - - # Look for class end. - brace_depth = classinfo.brace_depth - brace_depth = brace_depth + line.count('{') - line.count('}') - if brace_depth <= 0: - classinfo = classinfo_stack.pop() - # Try to detect missing virtual destructor declarations. - # For now, only warn if a non-derived class with virtual methods lacks - # a virtual destructor. This is to make it less likely that people will - # declare derived virtual destructors without declaring the base - # destructor virtual. - if ((classinfo.virtual_method_line_number is not None) - and (not classinfo.has_virtual_destructor) - and (not classinfo.is_derived)): # Only warn for base classes - error(classinfo.line_number, 'runtime/virtual', 4, - 'The class %s probably needs a virtual destructor due to ' - 'having virtual method(s), one declared at line %d.' - % (classinfo.name, classinfo.virtual_method_line_number)) - else: - classinfo.brace_depth = brace_depth - - -def check_spacing_for_function_call(line, line_number, error): - """Checks for the correctness of various spacing around function calls. - - Args: - line: The text of the line to check. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - - # Since function calls often occur inside if/for/foreach/while/switch - # expressions - which have their own, more liberal conventions - we - # first see if we should be looking inside such an expression for a - # function call, to which we can apply more strict standards. - function_call = line # if there's no control flow construct, look at whole line - for pattern in (r'\bif\s*\((.*)\)\s*{', - r'\bfor\s*\((.*)\)\s*{', - r'\bforeach\s*\((.*)\)\s*{', - r'\bwhile\s*\((.*)\)\s*[{;]', - r'\bswitch\s*\((.*)\)\s*{'): - matched = search(pattern, line) - if matched: - function_call = matched.group(1) # look inside the parens for function calls - break - - # Except in if/for/foreach/while/switch, there should never be space - # immediately inside parens (eg "f( 3, 4 )"). We make an exception - # for nested parens ( (a+b) + c ). Likewise, there should never be - # a space before a ( when it's a function argument. I assume it's a - # function argument when the char before the whitespace is legal in - # a function name (alnum + _) and we're not starting a macro. Also ignore - # pointers and references to arrays and functions coz they're too tricky: - # we use a very simple way to recognize these: - # " (something)(maybe-something)" or - # " (something)(maybe-something," or - # " (something)[something]" - # Note that we assume the contents of [] to be short enough that - # they'll never need to wrap. - if ( # Ignore control structures. - not search(r'\b(if|for|foreach|while|switch|return|new|delete)\b', function_call) - # Ignore pointers/references to functions. - and not search(r' \([^)]+\)\([^)]*(\)|,$)', function_call) - # Ignore pointers/references to arrays. - and not search(r' \([^)]+\)\[[^\]]+\]', function_call)): - if search(r'\w\s*\([ \t](?!\s*\\$)', function_call): # a ( used for a fn call - error(line_number, 'whitespace/parens', 4, - 'Extra space after ( in function call') - elif search(r'\([ \t]+(?!(\s*\\)|\()', function_call): - error(line_number, 'whitespace/parens', 2, - 'Extra space after (') - if (search(r'\w\s+\(', function_call) - and not search(r'#\s*define|typedef', function_call)): - error(line_number, 'whitespace/parens', 4, - 'Extra space before ( in function call') - # If the ) is followed only by a newline or a { + newline, assume it's - # part of a control statement (if/while/etc), and don't complain - if search(r'[^)\s]\s+\)(?!\s*$|{\s*$)', function_call): - error(line_number, 'whitespace/parens', 2, - 'Extra space before )') - - -def is_blank_line(line): - """Returns true if the given line is blank. - - We consider a line to be blank if the line is empty or consists of - only white spaces. - - Args: - line: A line of a string. - - Returns: - True, if the given line is blank. - """ - return not line or line.isspace() - - -def detect_functions(clean_lines, line_number, function_state, error): - """Finds where functions start and end. - - Uses a simplistic algorithm assuming other style guidelines - (especially spacing) are followed. - Trivial bodies are unchecked, so constructors with huge initializer lists - may be missed. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - function_state: Current function name and lines in body so far. - error: The function to call with any errors found. - """ - # Are we now past the end of a function? - if function_state.ending_line_number + 1 == line_number: - function_state.end() - - # If we're in a function, don't try to detect a new one. - if function_state.in_a_function: - return - - lines = clean_lines.lines - line = lines[line_number] - raw = clean_lines.raw_lines - raw_line = raw[line_number] - - regexp = r'\s*(\w(\w|::|\*|\&|\s|<|>|,|~)*)\(' # decls * & space::name( ... - match_result = match(regexp, line) - if not match_result: - return - - # If the name is all caps and underscores, figure it's a macro and - # ignore it, unless it's TEST or TEST_F. - function_name = match_result.group(1).split()[-1] - if function_name != 'TEST' and function_name != 'TEST_F' and match(r'[A-Z_]+$', function_name): - return - - joined_line = '' - for start_line_number in xrange(line_number, clean_lines.num_lines()): - start_line = lines[start_line_number] - joined_line += ' ' + start_line.lstrip() - if search(r'(;|})', start_line): # Declarations and trivial functions - return # ... ignore - - if search(r'{', start_line): - # Replace template constructs with _ so that no spaces remain in the function name, - # while keeping the column numbers of other characters the same as "line". - line_with_no_templates = iteratively_replace_matches_with_char(r'<[^<>]*>', '_', line) - match_function = search(r'((\w|:|<|>|,|~)*)\(', line_with_no_templates) - if not match_function: - return # The '(' must have been inside of a template. - - # Use the column numbers from the modified line to find the - # function name in the original line. - function = line[match_function.start(1):match_function.end(1)] - - if match(r'TEST', function): # Handle TEST... macros - parameter_regexp = search(r'(\(.*\))', joined_line) - if parameter_regexp: # Ignore bad syntax - function += parameter_regexp.group(1) - else: - function += '()' - open_brace_index = start_line.find('{') - ending_line_number = close_expression(clean_lines, start_line_number, open_brace_index)[1] - function_state.begin(function, start_line_number + 1, ending_line_number) - return - - # No body for the function (or evidence of a non-function) was found. - error(line_number, 'readability/fn_size', 5, - 'Lint failed to find start of function body.') - - -def check_for_function_lengths(clean_lines, line_number, function_state, error): - """Reports for long function bodies. - - For an overview why this is done, see: - http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions - - Blank/comment lines are not counted so as to avoid encouraging the removal - of vertical space and commments just to get through a lint check. - NOLINT *on the last line of a function* disables this check. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - function_state: Current function name and lines in body so far. - error: The function to call with any errors found. - """ - lines = clean_lines.lines - line = lines[line_number] - raw = clean_lines.raw_lines - raw_line = raw[line_number] - - if function_state.ending_line_number == line_number: # last line - if not search(r'\bNOLINT\b', raw_line): - function_state.check(error, line_number) - elif not match(r'^\s*$', line): - function_state.count(line_number) # Count non-blank/non-comment lines. - - -def check_pass_ptr_usage(clean_lines, line_number, function_state, error): - """Check for proper usage of Pass*Ptr. - - Currently this is limited to detecting declarations of Pass*Ptr - variables inside of functions. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - function_state: Current function name and lines in body so far. - error: The function to call with any errors found. - """ - if not function_state.in_a_function: - return - - lines = clean_lines.lines - line = lines[line_number] - if line_number >= function_state.body_start_line_number: - matched_pass_ptr = match(r'^\s*Pass([A-Z][A-Za-z]*)Ptr<', line) - if matched_pass_ptr: - type_name = 'Pass%sPtr' % matched_pass_ptr.group(1) - error(line_number, 'readability/pass_ptr', 5, - 'Local variables should never be %s (see ' - 'http://webkit.org/coding/RefPtr.html).' % type_name) - - -def check_spacing(file_extension, clean_lines, line_number, error): - """Checks for the correctness of various spacing issues in the code. - - Things we check for: spaces around operators, spaces after - if/for/while/switch, no spaces around parens in function calls, two - spaces between code and comment, don't start a block with a blank - line, don't end a function with a blank line, don't have too many - blank lines in a row. - - Args: - file_extension: The current file extension, without the leading dot. - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - - raw = clean_lines.raw_lines - line = raw[line_number] - - # Before nixing comments, check if the line is blank for no good - # reason. This includes the first line after a block is opened, and - # blank lines at the end of a function (ie, right before a line like '}'). - if is_blank_line(line): - elided = clean_lines.elided - previous_line = elided[line_number - 1] - previous_brace = previous_line.rfind('{') - # FIXME: Don't complain if line before blank line, and line after, - # both start with alnums and are indented the same amount. - # This ignores whitespace at the start of a namespace block - # because those are not usually indented. - if (previous_brace != -1 and previous_line[previous_brace:].find('}') == -1 - and previous_line[:previous_brace].find('namespace') == -1): - # OK, we have a blank line at the start of a code block. Before we - # complain, we check if it is an exception to the rule: The previous - # non-empty line has the parameters of a function header that are indented - # 4 spaces (because they did not fit in a 80 column line when placed on - # the same line as the function name). We also check for the case where - # the previous line is indented 6 spaces, which may happen when the - # initializers of a constructor do not fit into a 80 column line. - exception = False - if match(r' {6}\w', previous_line): # Initializer list? - # We are looking for the opening column of initializer list, which - # should be indented 4 spaces to cause 6 space indentation afterwards. - search_position = line_number - 2 - while (search_position >= 0 - and match(r' {6}\w', elided[search_position])): - search_position -= 1 - exception = (search_position >= 0 - and elided[search_position][:5] == ' :') - else: - # Search for the function arguments or an initializer list. We use a - # simple heuristic here: If the line is indented 4 spaces; and we have a - # closing paren, without the opening paren, followed by an opening brace - # or colon (for initializer lists) we assume that it is the last line of - # a function header. If we have a colon indented 4 spaces, it is an - # initializer list. - exception = (match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)', - previous_line) - or match(r' {4}:', previous_line)) - - if not exception: - error(line_number, 'whitespace/blank_line', 2, - 'Blank line at the start of a code block. Is this needed?') - # This doesn't ignore whitespace at the end of a namespace block - # because that is too hard without pairing open/close braces; - # however, a special exception is made for namespace closing - # brackets which have a comment containing "namespace". - # - # Also, ignore blank lines at the end of a block in a long if-else - # chain, like this: - # if (condition1) { - # // Something followed by a blank line - # - # } else if (condition2) { - # // Something else - # } - if line_number + 1 < clean_lines.num_lines(): - next_line = raw[line_number + 1] - if (next_line - and match(r'\s*}', next_line) - and next_line.find('namespace') == -1 - and next_line.find('} else ') == -1): - error(line_number, 'whitespace/blank_line', 3, - 'Blank line at the end of a code block. Is this needed?') - - # Next, we complain if there's a comment too near the text - comment_position = line.find('//') - if comment_position != -1: - # Check if the // may be in quotes. If so, ignore it - # Comparisons made explicit for clarity -- pylint: disable-msg=C6403 - if (line.count('"', 0, comment_position) - line.count('\\"', 0, comment_position)) % 2 == 0: # not in quotes - # Allow one space before end of line comment. - if (not match(r'^\s*$', line[:comment_position]) - and (comment_position >= 1 - and ((line[comment_position - 1] not in string.whitespace) - or (comment_position >= 2 - and line[comment_position - 2] in string.whitespace)))): - error(line_number, 'whitespace/comments', 5, - 'One space before end of line comments') - # There should always be a space between the // and the comment - commentend = comment_position + 2 - if commentend < len(line) and not line[commentend] == ' ': - # but some lines are exceptions -- e.g. if they're big - # comment delimiters like: - # //---------------------------------------------------------- - # or they begin with multiple slashes followed by a space: - # //////// Header comment - matched = (search(r'[=/-]{4,}\s*$', line[commentend:]) - or search(r'^/+ ', line[commentend:])) - if not matched: - error(line_number, 'whitespace/comments', 4, - 'Should have a space between // and comment') - - line = clean_lines.elided[line_number] # get rid of comments and strings - - # Don't try to do spacing checks for operator methods - line = sub(r'operator(==|!=|<|<<|<=|>=|>>|>|\+=|-=|\*=|/=|%=|&=|\|=|^=|<<=|>>=)\(', 'operator\(', line) - # Don't try to do spacing checks for #include or #import statements at - # minimum because it messes up checks for spacing around / - if match(r'\s*#\s*(?:include|import)', line): - return - if search(r'[\w.]=[\w.]', line): - error(line_number, 'whitespace/operators', 4, - 'Missing spaces around =') - - # FIXME: It's not ok to have spaces around binary operators like . - - # You should always have whitespace around binary operators. - # Alas, we can't test < or > because they're legitimately used sans spaces - # (a->b, vector<int> a). The only time we can tell is a < with no >, and - # only if it's not template params list spilling into the next line. - matched = search(r'[^<>=!\s](==|!=|\+=|-=|\*=|/=|/|\|=|&=|<<=|>>=|<=|>=|\|\||\||&&|>>|<<)[^<>=!\s]', line) - if not matched: - # Note that while it seems that the '<[^<]*' term in the following - # regexp could be simplified to '<.*', which would indeed match - # the same class of strings, the [^<] means that searching for the - # regexp takes linear rather than quadratic time. - if not search(r'<[^<]*,\s*$', line): # template params spill - matched = search(r'[^<>=!\s](<)[^<>=!\s]([^>]|->)*$', line) - if matched: - error(line_number, 'whitespace/operators', 3, - 'Missing spaces around %s' % matched.group(1)) - - # There shouldn't be space around unary operators - matched = search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line) - if matched: - error(line_number, 'whitespace/operators', 4, - 'Extra space for operator %s' % matched.group(1)) - - # A pet peeve of mine: no spaces after an if, while, switch, or for - matched = search(r' (if\(|for\(|foreach\(|while\(|switch\()', line) - if matched: - error(line_number, 'whitespace/parens', 5, - 'Missing space before ( in %s' % matched.group(1)) - - # For if/for/foreach/while/switch, the left and right parens should be - # consistent about how many spaces are inside the parens, and - # there should either be zero or one spaces inside the parens. - # We don't want: "if ( foo)" or "if ( foo )". - # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed. - matched = search(r'\b(?P<statement>if|for|foreach|while|switch)\s*\((?P<remainder>.*)$', line) - if matched: - statement = matched.group('statement') - condition, rest = up_to_unmatched_closing_paren(matched.group('remainder')) - if condition is not None: - condition_match = search(r'(?P<leading>[ ]*)(?P<separator>.).*[^ ]+(?P<trailing>[ ]*)', condition) - if condition_match: - n_leading = len(condition_match.group('leading')) - n_trailing = len(condition_match.group('trailing')) - if n_leading != 0: - for_exception = statement == 'for' and condition.startswith(' ;') - if not for_exception: - error(line_number, 'whitespace/parens', 5, - 'Extra space after ( in %s' % statement) - if n_trailing != 0: - for_exception = statement == 'for' and condition.endswith('; ') - if not for_exception: - error(line_number, 'whitespace/parens', 5, - 'Extra space before ) in %s' % statement) - - # Do not check for more than one command in macros - in_macro = match(r'\s*#define', line) - if not in_macro and not match(r'((\s*{\s*}?)|(\s*;?))\s*\\?$', rest): - error(line_number, 'whitespace/parens', 4, - 'More than one command on the same line in %s' % statement) - - # You should always have a space after a comma (either as fn arg or operator) - if search(r',[^\s]', line): - error(line_number, 'whitespace/comma', 3, - 'Missing space after ,') - - matched = search(r'^\s*(?P<token1>[a-zA-Z0-9_\*&]+)\s\s+(?P<token2>[a-zA-Z0-9_\*&]+)', line) - if matched: - error(line_number, 'whitespace/declaration', 3, - 'Extra space between %s and %s' % (matched.group('token1'), matched.group('token2'))) - - if file_extension == 'cpp': - # C++ should have the & or * beside the type not the variable name. - matched = match(r'\s*\w+(?<!\breturn|\bdelete)\s+(?P<pointer_operator>\*|\&)\w+', line) - if matched: - error(line_number, 'whitespace/declaration', 3, - 'Declaration has space between type name and %s in %s' % (matched.group('pointer_operator'), matched.group(0).strip())) - - elif file_extension == 'c': - # C Pointer declaration should have the * beside the variable not the type name. - matched = search(r'^\s*\w+\*\s+\w+', line) - if matched: - error(line_number, 'whitespace/declaration', 3, - 'Declaration has space between * and variable name in %s' % matched.group(0).strip()) - - # Next we will look for issues with function calls. - check_spacing_for_function_call(line, line_number, error) - - # Except after an opening paren, you should have spaces before your braces. - # And since you should never have braces at the beginning of a line, this is - # an easy test. - if search(r'[^ ({]{', line): - error(line_number, 'whitespace/braces', 5, - 'Missing space before {') - - # Make sure '} else {' has spaces. - if search(r'}else', line): - error(line_number, 'whitespace/braces', 5, - 'Missing space before else') - - # You shouldn't have spaces before your brackets, except maybe after - # 'delete []' or 'new char * []'. - if search(r'\w\s+\[', line) and not search(r'delete\s+\[', line): - error(line_number, 'whitespace/braces', 5, - 'Extra space before [') - - # You shouldn't have a space before a semicolon at the end of the line. - # There's a special case for "for" since the style guide allows space before - # the semicolon there. - if search(r':\s*;\s*$', line): - error(line_number, 'whitespace/semicolon', 5, - 'Semicolon defining empty statement. Use { } instead.') - elif search(r'^\s*;\s*$', line): - error(line_number, 'whitespace/semicolon', 5, - 'Line contains only semicolon. If this should be an empty statement, ' - 'use { } instead.') - elif (search(r'\s+;\s*$', line) and not search(r'\bfor\b', line)): - error(line_number, 'whitespace/semicolon', 5, - 'Extra space before last semicolon. If this should be an empty ' - 'statement, use { } instead.') - elif (search(r'\b(for|while)\s*\(.*\)\s*;\s*$', line) - and line.count('(') == line.count(')') - # Allow do {} while(); - and not search(r'}\s*while', line)): - error(line_number, 'whitespace/semicolon', 5, - 'Semicolon defining empty statement for this loop. Use { } instead.') - - -def get_previous_non_blank_line(clean_lines, line_number): - """Return the most recent non-blank line and its line number. - - Args: - clean_lines: A CleansedLines instance containing the file contents. - line_number: The number of the line to check. - - Returns: - A tuple with two elements. The first element is the contents of the last - non-blank line before the current line, or the empty string if this is the - first non-blank line. The second is the line number of that line, or -1 - if this is the first non-blank line. - """ - - previous_line_number = line_number - 1 - while previous_line_number >= 0: - previous_line = clean_lines.elided[previous_line_number] - if not is_blank_line(previous_line): # if not a blank line... - return (previous_line, previous_line_number) - previous_line_number -= 1 - return ('', -1) - - -def check_namespace_indentation(clean_lines, line_number, file_extension, file_state, error): - """Looks for indentation errors inside of namespaces. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - file_extension: The extension (dot not included) of the file. - file_state: A _FileState instance which maintains information about - the state of things in the file. - error: The function to call with any errors found. - """ - - line = clean_lines.elided[line_number] # Get rid of comments and strings. - - namespace_match = match(r'(?P<namespace_indentation>\s*)namespace\s+\S+\s*{\s*$', line) - if not namespace_match: - return - - current_indentation_level = len(namespace_match.group('namespace_indentation')) - if current_indentation_level > 0: - # Don't warn about an indented namespace if we already warned about indented code. - if not file_state.did_inside_namespace_indent_warning(): - error(line_number, 'whitespace/indent', 4, - 'namespace should never be indented.') - return - looking_for_semicolon = False; - line_offset = 0 - in_preprocessor_directive = False; - for current_line in clean_lines.elided[line_number + 1:]: - line_offset += 1 - if not current_line.strip(): - continue - if not current_indentation_level: - if not (in_preprocessor_directive or looking_for_semicolon): - if not match(r'\S', current_line) and not file_state.did_inside_namespace_indent_warning(): - file_state.set_did_inside_namespace_indent_warning() - error(line_number + line_offset, 'whitespace/indent', 4, - 'Code inside a namespace should not be indented.') - if in_preprocessor_directive or (current_line.strip()[0] == '#'): # This takes care of preprocessor directive syntax. - in_preprocessor_directive = current_line[-1] == '\\' - else: - looking_for_semicolon = ((current_line.find(';') == -1) and (current_line.strip()[-1] != '}')) or (current_line[-1] == '\\') - else: - looking_for_semicolon = False; # If we have a brace we may not need a semicolon. - current_indentation_level += current_line.count('{') - current_line.count('}') - if current_indentation_level < 0: - break; - -def check_using_std(file_extension, clean_lines, line_number, error): - """Looks for 'using std::foo;' statements which should be replaced with 'using namespace std;'. - - Args: - file_extension: The extension of the current file, without the leading dot. - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - - # This check doesn't apply to C or Objective-C implementation files. - if is_c_or_objective_c(file_extension): - return - - line = clean_lines.elided[line_number] # Get rid of comments and strings. - - using_std_match = match(r'\s*using\s+std::(?P<method_name>\S+)\s*;\s*$', line) - if not using_std_match: - return - - method_name = using_std_match.group('method_name') - error(line_number, 'build/using_std', 4, - "Use 'using namespace std;' instead of 'using std::%s;'." % method_name) - - -def check_max_min_macros(file_extension, clean_lines, line_number, error): - """Looks use of MAX() and MIN() macros that should be replaced with std::max() and std::min(). - - Args: - file_extension: The extension of the current file, without the leading dot. - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - - # This check doesn't apply to C or Objective-C implementation files. - if is_c_or_objective_c(file_extension): - return - - line = clean_lines.elided[line_number] # Get rid of comments and strings. - - max_min_macros_search = search(r'\b(?P<max_min_macro>(MAX|MIN))\s*\(', line) - if not max_min_macros_search: - return - - max_min_macro = max_min_macros_search.group('max_min_macro') - max_min_macro_lower = max_min_macro.lower() - error(line_number, 'runtime/max_min_macros', 4, - 'Use std::%s() or std::%s<type>() instead of the %s() macro.' - % (max_min_macro_lower, max_min_macro_lower, max_min_macro)) - - -def check_switch_indentation(clean_lines, line_number, error): - """Looks for indentation errors inside of switch statements. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - - line = clean_lines.elided[line_number] # Get rid of comments and strings. - - switch_match = match(r'(?P<switch_indentation>\s*)switch\s*\(.+\)\s*{\s*$', line) - if not switch_match: - return - - switch_indentation = switch_match.group('switch_indentation') - inner_indentation = switch_indentation + ' ' * 4 - line_offset = 0 - encountered_nested_switch = False - - for current_line in clean_lines.elided[line_number + 1:]: - line_offset += 1 - - # Skip not only empty lines but also those with preprocessor directives. - if current_line.strip() == '' or current_line.startswith('#'): - continue - - if match(r'\s*switch\s*\(.+\)\s*{\s*$', current_line): - # Complexity alarm - another switch statement nested inside the one - # that we're currently testing. We'll need to track the extent of - # that inner switch if the upcoming label tests are still supposed - # to work correctly. Let's not do that; instead, we'll finish - # checking this line, and then leave it like that. Assuming the - # indentation is done consistently (even if incorrectly), this will - # still catch all indentation issues in practice. - encountered_nested_switch = True - - current_indentation_match = match(r'(?P<indentation>\s*)(?P<remaining_line>.*)$', current_line); - current_indentation = current_indentation_match.group('indentation') - remaining_line = current_indentation_match.group('remaining_line') - - # End the check at the end of the switch statement. - if remaining_line.startswith('}') and current_indentation == switch_indentation: - break - # Case and default branches should not be indented. The regexp also - # catches single-line cases like "default: break;" but does not trigger - # on stuff like "Document::Foo();". - elif match(r'(default|case\s+.*)\s*:([^:].*)?$', remaining_line): - if current_indentation != switch_indentation: - error(line_number + line_offset, 'whitespace/indent', 4, - 'A case label should not be indented, but line up with its switch statement.') - # Don't throw an error for multiple badly indented labels, - # one should be enough to figure out the problem. - break - # We ignore goto labels at the very beginning of a line. - elif match(r'\w+\s*:\s*$', remaining_line): - continue - # It's not a goto label, so check if it's indented at least as far as - # the switch statement plus one more level of indentation. - elif not current_indentation.startswith(inner_indentation): - error(line_number + line_offset, 'whitespace/indent', 4, - 'Non-label code inside switch statements should be indented.') - # Don't throw an error for multiple badly indented statements, - # one should be enough to figure out the problem. - break - - if encountered_nested_switch: - break - - -def check_braces(clean_lines, line_number, error): - """Looks for misplaced braces (e.g. at the end of line). - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - - line = clean_lines.elided[line_number] # Get rid of comments and strings. - - if match(r'\s*{\s*$', line): - # We allow an open brace to start a line in the case where someone - # is using braces for function definition or in a block to - # explicitly create a new scope, which is commonly used to control - # the lifetime of stack-allocated variables. We don't detect this - # perfectly: we just don't complain if the last non-whitespace - # character on the previous non-blank line is ';', ':', '{', '}', - # ')', or ') const' and doesn't begin with 'if|for|while|switch|else'. - # We also allow '#' for #endif and '=' for array initialization. - previous_line = get_previous_non_blank_line(clean_lines, line_number)[0] - if ((not search(r'[;:}{)=]\s*$|\)\s*const\s*$', previous_line) - or search(r'\b(if|for|foreach|while|switch|else)\b', previous_line)) - and previous_line.find('#') < 0): - error(line_number, 'whitespace/braces', 4, - 'This { should be at the end of the previous line') - elif (search(r'\)\s*(const\s*)?{\s*$', line) - and line.count('(') == line.count(')') - and not search(r'\b(if|for|foreach|while|switch)\b', line) - and not match(r'\s+[A-Z_][A-Z_0-9]+\b', line)): - error(line_number, 'whitespace/braces', 4, - 'Place brace on its own line for function definitions.') - - if (match(r'\s*}\s*(else\s*({\s*)?)?$', line) and line_number > 1): - # We check if a closed brace has started a line to see if a - # one line control statement was previous. - previous_line = clean_lines.elided[line_number - 2] - if (previous_line.find('{') > 0 and previous_line.find('}') < 0 - and search(r'\b(if|for|foreach|while|else)\b', previous_line)): - error(line_number, 'whitespace/braces', 4, - 'One line control clauses should not use braces.') - - # An else clause should be on the same line as the preceding closing brace. - if match(r'\s*else\s*', line): - previous_line = get_previous_non_blank_line(clean_lines, line_number)[0] - if match(r'\s*}\s*$', previous_line): - error(line_number, 'whitespace/newline', 4, - 'An else should appear on the same line as the preceding }') - - # Likewise, an else should never have the else clause on the same line - if search(r'\belse [^\s{]', line) and not search(r'\belse if\b', line): - error(line_number, 'whitespace/newline', 4, - 'Else clause should never be on same line as else (use 2 lines)') - - # In the same way, a do/while should never be on one line - if match(r'\s*do [^\s{]', line): - error(line_number, 'whitespace/newline', 4, - 'do/while clauses should not be on a single line') - - # Braces shouldn't be followed by a ; unless they're defining a struct - # or initializing an array. - # We can't tell in general, but we can for some common cases. - previous_line_number = line_number - while True: - (previous_line, previous_line_number) = get_previous_non_blank_line(clean_lines, previous_line_number) - if match(r'\s+{.*}\s*;', line) and not previous_line.count(';'): - line = previous_line + line - else: - break - if (search(r'{.*}\s*;', line) - and line.count('{') == line.count('}') - and not search(r'struct|class|enum|\s*=\s*{', line)): - error(line_number, 'readability/braces', 4, - "You don't need a ; after a }") - - -def check_exit_statement_simplifications(clean_lines, line_number, error): - """Looks for else or else-if statements that should be written as an - if statement when the prior if concludes with a return, break, continue or - goto statement. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - - line = clean_lines.elided[line_number] # Get rid of comments and strings. - - else_match = match(r'(?P<else_indentation>\s*)(\}\s*)?else(\s+if\s*\(|(?P<else>\s*(\{\s*)?\Z))', line) - if not else_match: - return - - else_indentation = else_match.group('else_indentation') - inner_indentation = else_indentation + ' ' * 4 - - previous_lines = clean_lines.elided[:line_number] - previous_lines.reverse() - line_offset = 0 - encountered_exit_statement = False - - for current_line in previous_lines: - line_offset -= 1 - - # Skip not only empty lines but also those with preprocessor directives - # and goto labels. - if current_line.strip() == '' or current_line.startswith('#') or match(r'\w+\s*:\s*$', current_line): - continue - - # Skip lines with closing braces on the original indentation level. - # Even though the styleguide says they should be on the same line as - # the "else if" statement, we also want to check for instances where - # the current code does not comply with the coding style. Thus, ignore - # these lines and proceed to the line before that. - if current_line == else_indentation + '}': - continue - - current_indentation_match = match(r'(?P<indentation>\s*)(?P<remaining_line>.*)$', current_line); - current_indentation = current_indentation_match.group('indentation') - remaining_line = current_indentation_match.group('remaining_line') - - # As we're going up the lines, the first real statement to encounter - # has to be an exit statement (return, break, continue or goto) - - # otherwise, this check doesn't apply. - if not encountered_exit_statement: - # We only want to find exit statements if they are on exactly - # the same level of indentation as expected from the code inside - # the block. If the indentation doesn't strictly match then we - # might have a nested if or something, which must be ignored. - if current_indentation != inner_indentation: - break - if match(r'(return(\W+.*)|(break|continue)\s*;|goto\s*\w+;)$', remaining_line): - encountered_exit_statement = True - continue - break - - # When code execution reaches this point, we've found an exit statement - # as last statement of the previous block. Now we only need to make - # sure that the block belongs to an "if", then we can throw an error. - - # Skip lines with opening braces on the original indentation level, - # similar to the closing braces check above. ("if (condition)\n{") - if current_line == else_indentation + '{': - continue - - # Skip everything that's further indented than our "else" or "else if". - if current_indentation.startswith(else_indentation) and current_indentation != else_indentation: - continue - - # So we've got a line with same (or less) indentation. Is it an "if"? - # If yes: throw an error. If no: don't throw an error. - # Whatever the outcome, this is the end of our loop. - if match(r'if\s*\(', remaining_line): - if else_match.start('else') != -1: - error(line_number + line_offset, 'readability/control_flow', 4, - 'An else statement can be removed when the prior "if" ' - 'concludes with a return, break, continue or goto statement.') - else: - error(line_number + line_offset, 'readability/control_flow', 4, - 'An else if statement should be written as an if statement ' - 'when the prior "if" concludes with a return, break, ' - 'continue or goto statement.') - break - - -def replaceable_check(operator, macro, line): - """Determine whether a basic CHECK can be replaced with a more specific one. - - For example suggest using CHECK_EQ instead of CHECK(a == b) and - similarly for CHECK_GE, CHECK_GT, CHECK_LE, CHECK_LT, CHECK_NE. - - Args: - operator: The C++ operator used in the CHECK. - macro: The CHECK or EXPECT macro being called. - line: The current source line. - - Returns: - True if the CHECK can be replaced with a more specific one. - """ - - # This matches decimal and hex integers, strings, and chars (in that order). - match_constant = r'([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')' - - # Expression to match two sides of the operator with something that - # looks like a literal, since CHECK(x == iterator) won't compile. - # This means we can't catch all the cases where a more specific - # CHECK is possible, but it's less annoying than dealing with - # extraneous warnings. - match_this = (r'\s*' + macro + r'\((\s*' + - match_constant + r'\s*' + operator + r'[^<>].*|' - r'.*[^<>]' + operator + r'\s*' + match_constant + - r'\s*\))') - - # Don't complain about CHECK(x == NULL) or similar because - # CHECK_EQ(x, NULL) won't compile (requires a cast). - # Also, don't complain about more complex boolean expressions - # involving && or || such as CHECK(a == b || c == d). - return match(match_this, line) and not search(r'NULL|&&|\|\|', line) - - -def check_check(clean_lines, line_number, error): - """Checks the use of CHECK and EXPECT macros. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - error: The function to call with any errors found. - """ - - # Decide the set of replacement macros that should be suggested - raw_lines = clean_lines.raw_lines - current_macro = '' - for macro in _CHECK_MACROS: - if raw_lines[line_number].find(macro) >= 0: - current_macro = macro - break - if not current_macro: - # Don't waste time here if line doesn't contain 'CHECK' or 'EXPECT' - return - - line = clean_lines.elided[line_number] # get rid of comments and strings - - # Encourage replacing plain CHECKs with CHECK_EQ/CHECK_NE/etc. - for operator in ['==', '!=', '>=', '>', '<=', '<']: - if replaceable_check(operator, current_macro, line): - error(line_number, 'readability/check', 2, - 'Consider using %s instead of %s(a %s b)' % ( - _CHECK_REPLACEMENT[current_macro][operator], - current_macro, operator)) - break - - -def check_for_comparisons_to_zero(clean_lines, line_number, error): - # Get the line without comments and strings. - line = clean_lines.elided[line_number] - - # Include NULL here so that users don't have to convert NULL to 0 first and then get this error. - if search(r'[=!]=\s*(NULL|0|true|false)\W', line) or search(r'\W(NULL|0|true|false)\s*[=!]=', line): - error(line_number, 'readability/comparison_to_zero', 5, - 'Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.') - - -def check_for_null(file_extension, clean_lines, line_number, error): - # This check doesn't apply to C or Objective-C implementation files. - if is_c_or_objective_c(file_extension): - return - - line = clean_lines.elided[line_number] - - # Don't warn about NULL usage in g_*(). See Bug 32858 and 39372. - if search(r'\bg(_[a-z]+)+\b', line): - return - - # Don't warn about NULL usage in gst_*_many(). See Bug 39740 - if search(r'\bgst_\w+_many\b', line): - return - - # Don't warn about NULL usage in g_str{join,concat}(). See Bug 34834 - if search(r'\bg_str(join|concat)\b', line): - return - - # Don't warn about NULL usage in gdk_pixbuf_save_to_*{join,concat}(). See Bug 43090. - if search(r'\bgdk_pixbuf_save_to\w+\b', line): - return - - if search(r'\bNULL\b', line): - error(line_number, 'readability/null', 5, 'Use 0 instead of NULL.') - return - - line = clean_lines.raw_lines[line_number] - # See if NULL occurs in any comments in the line. If the search for NULL using the raw line - # matches, then do the check with strings collapsed to avoid giving errors for - # NULLs occurring in strings. - if search(r'\bNULL\b', line) and search(r'\bNULL\b', CleansedLines.collapse_strings(line)): - error(line_number, 'readability/null', 4, 'Use 0 instead of NULL.') - -def get_line_width(line): - """Determines the width of the line in column positions. - - Args: - line: A string, which may be a Unicode string. - - Returns: - The width of the line in column positions, accounting for Unicode - combining characters and wide characters. - """ - if isinstance(line, unicode): - width = 0 - for c in unicodedata.normalize('NFC', line): - if unicodedata.east_asian_width(c) in ('W', 'F'): - width += 2 - elif not unicodedata.combining(c): - width += 1 - return width - return len(line) - - -def check_style(clean_lines, line_number, file_extension, class_state, file_state, error): - """Checks rules from the 'C++ style rules' section of cppguide.html. - - Most of these rules are hard to test (naming, comment style), but we - do what we can. In particular we check for 4-space indents, line lengths, - tab usage, spaces inside code, etc. - - Args: - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - file_extension: The extension (without the dot) of the filename. - class_state: A _ClassState instance which maintains information about - the current stack of nested class declarations being parsed. - file_state: A _FileState instance which maintains information about - the state of things in the file. - error: The function to call with any errors found. - """ - - raw_lines = clean_lines.raw_lines - line = raw_lines[line_number] - - if line.find('\t') != -1: - error(line_number, 'whitespace/tab', 1, - 'Tab found; better to use spaces') - - # One or three blank spaces at the beginning of the line is weird; it's - # hard to reconcile that with 4-space indents. - # NOTE: here are the conditions rob pike used for his tests. Mine aren't - # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces - # if(RLENGTH > 20) complain = 0; - # if(match($0, " +(error|private|public|protected):")) complain = 0; - # if(match(prev, "&& *$")) complain = 0; - # if(match(prev, "\\|\\| *$")) complain = 0; - # if(match(prev, "[\",=><] *$")) complain = 0; - # if(match($0, " <<")) complain = 0; - # if(match(prev, " +for \\(")) complain = 0; - # if(prevodd && match(prevprev, " +for \\(")) complain = 0; - initial_spaces = 0 - cleansed_line = clean_lines.elided[line_number] - while initial_spaces < len(line) and line[initial_spaces] == ' ': - initial_spaces += 1 - if line and line[-1].isspace(): - error(line_number, 'whitespace/end_of_line', 4, - 'Line ends in whitespace. Consider deleting these extra spaces.') - # There are certain situations we allow one space, notably for labels - elif ((initial_spaces >= 1 and initial_spaces <= 3) - and not match(r'\s*\w+\s*:\s*$', cleansed_line)): - error(line_number, 'whitespace/indent', 3, - 'Weird number of spaces at line-start. ' - 'Are you using a 4-space indent?') - # Labels should always be indented at least one space. - elif not initial_spaces and line[:2] != '//': - label_match = match(r'(?P<label>[^:]+):\s*$', line) - - if label_match: - label = label_match.group('label') - # Only throw errors for stuff that is definitely not a goto label, - # because goto labels can in fact occur at the start of the line. - if label in ['public', 'private', 'protected'] or label.find(' ') != -1: - error(line_number, 'whitespace/labels', 4, - 'Labels should always be indented at least one space. ' - 'If this is a member-initializer list in a constructor, ' - 'the colon should be on the line after the definition header.') - - if (cleansed_line.count(';') > 1 - # for loops are allowed two ;'s (and may run over two lines). - and cleansed_line.find('for') == -1 - and (get_previous_non_blank_line(clean_lines, line_number)[0].find('for') == -1 - or get_previous_non_blank_line(clean_lines, line_number)[0].find(';') != -1) - # It's ok to have many commands in a switch case that fits in 1 line - and not ((cleansed_line.find('case ') != -1 - or cleansed_line.find('default:') != -1) - and cleansed_line.find('break;') != -1) - # Also it's ok to have many commands in trivial single-line accessors in class definitions. - and not (match(r'.*\(.*\).*{.*.}', line) - and class_state.classinfo_stack - and line.count('{') == line.count('}')) - and not cleansed_line.startswith('#define ')): - error(line_number, 'whitespace/newline', 4, - 'More than one command on the same line') - - if cleansed_line.strip().endswith('||') or cleansed_line.strip().endswith('&&'): - error(line_number, 'whitespace/operators', 4, - 'Boolean expressions that span multiple lines should have their ' - 'operators on the left side of the line instead of the right side.') - - # Some more style checks - check_namespace_indentation(clean_lines, line_number, file_extension, file_state, error) - check_using_std(file_extension, clean_lines, line_number, error) - check_max_min_macros(file_extension, clean_lines, line_number, error) - check_switch_indentation(clean_lines, line_number, error) - check_braces(clean_lines, line_number, error) - check_exit_statement_simplifications(clean_lines, line_number, error) - check_spacing(file_extension, clean_lines, line_number, error) - check_check(clean_lines, line_number, error) - check_for_comparisons_to_zero(clean_lines, line_number, error) - check_for_null(file_extension, clean_lines, line_number, error) - - -_RE_PATTERN_INCLUDE_NEW_STYLE = re.compile(r'#include +"[^/]+\.h"') -_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]*)[>"].*$') -# Matches the first component of a filename delimited by -s and _s. That is: -# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo' -# _RE_FIRST_COMPONENT.match('foo.cpp').group(0) == 'foo' -# _RE_FIRST_COMPONENT.match('foo-bar_baz.cpp').group(0) == 'foo' -# _RE_FIRST_COMPONENT.match('foo_bar-baz.cpp').group(0) == 'foo' -_RE_FIRST_COMPONENT = re.compile(r'^[^-_.]+') - - -def _drop_common_suffixes(filename): - """Drops common suffixes like _test.cpp or -inl.h from filename. - - For example: - >>> _drop_common_suffixes('foo/foo-inl.h') - 'foo/foo' - >>> _drop_common_suffixes('foo/bar/foo.cpp') - 'foo/bar/foo' - >>> _drop_common_suffixes('foo/foo_internal.h') - 'foo/foo' - >>> _drop_common_suffixes('foo/foo_unusualinternal.h') - 'foo/foo_unusualinternal' - - Args: - filename: The input filename. - - Returns: - The filename with the common suffix removed. - """ - for suffix in ('test.cpp', 'regtest.cpp', 'unittest.cpp', - 'inl.h', 'impl.h', 'internal.h'): - if (filename.endswith(suffix) and len(filename) > len(suffix) - and filename[-len(suffix) - 1] in ('-', '_')): - return filename[:-len(suffix) - 1] - return os.path.splitext(filename)[0] - - -def _classify_include(filename, include, is_system, include_state): - """Figures out what kind of header 'include' is. - - Args: - filename: The current file cpp_style is running over. - include: The path to a #included file. - is_system: True if the #include used <> rather than "". - include_state: An _IncludeState instance in which the headers are inserted. - - Returns: - One of the _XXX_HEADER constants. - - For example: - >>> _classify_include('foo.cpp', 'config.h', False) - _CONFIG_HEADER - >>> _classify_include('foo.cpp', 'foo.h', False) - _PRIMARY_HEADER - >>> _classify_include('foo.cpp', 'bar.h', False) - _OTHER_HEADER - """ - - # If it is a system header we know it is classified as _OTHER_HEADER. - if is_system: - return _OTHER_HEADER - - # If the include is named config.h then this is WebCore/config.h. - if include == "config.h": - return _CONFIG_HEADER - - # There cannot be primary includes in header files themselves. Only an - # include exactly matches the header filename will be is flagged as - # primary, so that it triggers the "don't include yourself" check. - if filename.endswith('.h') and filename != include: - return _OTHER_HEADER; - - # Qt's moc files do not follow the naming and ordering rules, so they should be skipped - if include.startswith('moc_') and include.endswith('.cpp'): - return _MOC_HEADER - - if include.endswith('.moc'): - return _MOC_HEADER - - # If the target file basename starts with the include we're checking - # then we consider it the primary header. - target_base = FileInfo(filename).base_name() - include_base = FileInfo(include).base_name() - - # If we haven't encountered a primary header, then be lenient in checking. - if not include_state.visited_primary_section() and target_base.find(include_base) != -1: - return _PRIMARY_HEADER - # If we already encountered a primary header, perform a strict comparison. - # In case the two filename bases are the same then the above lenient check - # probably was a false positive. - elif include_state.visited_primary_section() and target_base == include_base: - if include == "ResourceHandleWin.h": - # FIXME: Thus far, we've only seen one example of these, but if we - # start to see more, please consider generalizing this check - # somehow. - return _OTHER_HEADER - return _PRIMARY_HEADER - - return _OTHER_HEADER - - -def check_include_line(filename, file_extension, clean_lines, line_number, include_state, error): - """Check rules that are applicable to #include lines. - - Strings on #include lines are NOT removed from elided line, to make - certain tasks easier. However, to prevent false positives, checks - applicable to #include lines in CheckLanguage must be put here. - - Args: - filename: The name of the current file. - file_extension: The current file extension, without the leading dot. - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - include_state: An _IncludeState instance in which the headers are inserted. - error: The function to call with any errors found. - """ - # FIXME: For readability or as a possible optimization, consider - # exiting early here by checking whether the "build/include" - # category should be checked for the given filename. This - # may involve having the error handler classes expose a - # should_check() method, in addition to the usual __call__ - # method. - line = clean_lines.lines[line_number] - - matched = _RE_PATTERN_INCLUDE.search(line) - if not matched: - return - - include = matched.group(2) - is_system = (matched.group(1) == '<') - - # Look for any of the stream classes that are part of standard C++. - if match(r'(f|ind|io|i|o|parse|pf|stdio|str|)?stream$', include): - error(line_number, 'readability/streams', 3, - 'Streams are highly discouraged.') - - # Look for specific includes to fix. - if include.startswith('wtf/') and not is_system: - error(line_number, 'build/include', 4, - 'wtf includes should be <wtf/file.h> instead of "wtf/file.h".') - - duplicate_header = include in include_state - if duplicate_header: - error(line_number, 'build/include', 4, - '"%s" already included at %s:%s' % - (include, filename, include_state[include])) - else: - include_state[include] = line_number - - header_type = _classify_include(filename, include, is_system, include_state) - include_state.header_types[line_number] = header_type - - # Only proceed if this isn't a duplicate header. - if duplicate_header: - return - - # We want to ensure that headers appear in the right order: - # 1) for implementation files: config.h, primary header, blank line, alphabetically sorted - # 2) for header files: alphabetically sorted - # The include_state object keeps track of the last type seen - # and complains if the header types are out of order or missing. - error_message = include_state.check_next_include_order(header_type, file_extension == "h") - - # Check to make sure we have a blank line after primary header. - if not error_message and header_type == _PRIMARY_HEADER: - next_line = clean_lines.raw_lines[line_number + 1] - if not is_blank_line(next_line): - error(line_number, 'build/include_order', 4, - 'You should add a blank line after implementation file\'s own header.') - - # Check to make sure all headers besides config.h and the primary header are - # alphabetically sorted. Skip Qt's moc files. - if not error_message and header_type == _OTHER_HEADER: - previous_line_number = line_number - 1; - previous_line = clean_lines.lines[previous_line_number] - previous_match = _RE_PATTERN_INCLUDE.search(previous_line) - while (not previous_match and previous_line_number > 0 - and not search(r'\A(#if|#ifdef|#ifndef|#else|#elif|#endif)', previous_line)): - previous_line_number -= 1; - previous_line = clean_lines.lines[previous_line_number] - previous_match = _RE_PATTERN_INCLUDE.search(previous_line) - if previous_match: - previous_header_type = include_state.header_types[previous_line_number] - if previous_header_type == _OTHER_HEADER and previous_line.strip() > line.strip(): - error(line_number, 'build/include_order', 4, - 'Alphabetical sorting problem.') - - if error_message: - if file_extension == 'h': - error(line_number, 'build/include_order', 4, - '%s Should be: alphabetically sorted.' % - error_message) - else: - error(line_number, 'build/include_order', 4, - '%s Should be: config.h, primary header, blank line, and then alphabetically sorted.' % - error_message) - - -def check_language(filename, clean_lines, line_number, file_extension, include_state, - error): - """Checks rules from the 'C++ language rules' section of cppguide.html. - - Some of these rules are hard to test (function overloading, using - uint32 inappropriately), but we do the best we can. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - line_number: The number of the line to check. - file_extension: The extension (without the dot) of the filename. - include_state: An _IncludeState instance in which the headers are inserted. - error: The function to call with any errors found. - """ - # If the line is empty or consists of entirely a comment, no need to - # check it. - line = clean_lines.elided[line_number] - if not line: - return - - matched = _RE_PATTERN_INCLUDE.search(line) - if matched: - check_include_line(filename, file_extension, clean_lines, line_number, include_state, error) - return - - # FIXME: figure out if they're using default arguments in fn proto. - - # Check to see if they're using an conversion function cast. - # I just try to capture the most common basic types, though there are more. - # Parameterless conversion functions, such as bool(), are allowed as they are - # probably a member operator declaration or default constructor. - matched = search( - r'\b(int|float|double|bool|char|int32|uint32|int64|uint64)\([^)]', line) - if matched: - # gMock methods are defined using some variant of MOCK_METHODx(name, type) - # where type may be float(), int(string), etc. Without context they are - # virtually indistinguishable from int(x) casts. - if not match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line): - error(line_number, 'readability/casting', 4, - 'Using deprecated casting style. ' - 'Use static_cast<%s>(...) instead' % - matched.group(1)) - - check_c_style_cast(line_number, line, clean_lines.raw_lines[line_number], - 'static_cast', - r'\((int|float|double|bool|char|u?int(16|32|64))\)', - error) - # This doesn't catch all cases. Consider (const char * const)"hello". - check_c_style_cast(line_number, line, clean_lines.raw_lines[line_number], - 'reinterpret_cast', r'\((\w+\s?\*+\s?)\)', error) - - # In addition, we look for people taking the address of a cast. This - # is dangerous -- casts can assign to temporaries, so the pointer doesn't - # point where you think. - if search( - r'(&\([^)]+\)[\w(])|(&(static|dynamic|reinterpret)_cast\b)', line): - error(line_number, 'runtime/casting', 4, - ('Are you taking an address of a cast? ' - 'This is dangerous: could be a temp var. ' - 'Take the address before doing the cast, rather than after')) - - # Check for people declaring static/global STL strings at the top level. - # This is dangerous because the C++ language does not guarantee that - # globals with constructors are initialized before the first access. - matched = match( - r'((?:|static +)(?:|const +))string +([a-zA-Z0-9_:]+)\b(.*)', - line) - # Make sure it's not a function. - # Function template specialization looks like: "string foo<Type>(...". - # Class template definitions look like: "string Foo<Type>::Method(...". - if matched and not match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)?\s*\(([^"]|$)', - matched.group(3)): - error(line_number, 'runtime/string', 4, - 'For a static/global string constant, use a C style string instead: ' - '"%schar %s[]".' % - (matched.group(1), matched.group(2))) - - # Check that we're not using RTTI outside of testing code. - if search(r'\bdynamic_cast<', line): - error(line_number, 'runtime/rtti', 5, - 'Do not use dynamic_cast<>. If you need to cast within a class ' - "hierarchy, use static_cast<> to upcast. Google doesn't support " - 'RTTI.') - - if search(r'\b([A-Za-z0-9_]*_)\(\1\)', line): - error(line_number, 'runtime/init', 4, - 'You seem to be initializing a member variable with itself.') - - if file_extension == 'h': - # FIXME: check that 1-arg constructors are explicit. - # How to tell it's a constructor? - # (handled in check_for_non_standard_constructs for now) - pass - - # Check if people are using the verboten C basic types. The only exception - # we regularly allow is "unsigned short port" for port. - if search(r'\bshort port\b', line): - if not search(r'\bunsigned short port\b', line): - error(line_number, 'runtime/int', 4, - 'Use "unsigned short" for ports, not "short"') - - # When snprintf is used, the second argument shouldn't be a literal. - matched = search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line) - if matched: - error(line_number, 'runtime/printf', 3, - 'If you can, use sizeof(%s) instead of %s as the 2nd arg ' - 'to snprintf.' % (matched.group(1), matched.group(2))) - - # Check if some verboten C functions are being used. - if search(r'\bsprintf\b', line): - error(line_number, 'runtime/printf', 5, - 'Never use sprintf. Use snprintf instead.') - matched = search(r'\b(strcpy|strcat)\b', line) - if matched: - error(line_number, 'runtime/printf', 4, - 'Almost always, snprintf is better than %s' % matched.group(1)) - - if search(r'\bsscanf\b', line): - error(line_number, 'runtime/printf', 1, - 'sscanf can be ok, but is slow and can overflow buffers.') - - # Check for suspicious usage of "if" like - # } if (a == b) { - if search(r'\}\s*if\s*\(', line): - error(line_number, 'readability/braces', 4, - 'Did you mean "else if"? If not, start a new line for "if".') - - # Check for potential format string bugs like printf(foo). - # We constrain the pattern not to pick things like DocidForPrintf(foo). - # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str()) - matched = re.search(r'\b((?:string)?printf)\s*\(([\w.\->()]+)\)', line, re.I) - if matched: - error(line_number, 'runtime/printf', 4, - 'Potential format string bug. Do %s("%%s", %s) instead.' - % (matched.group(1), matched.group(2))) - - # Check for potential memset bugs like memset(buf, sizeof(buf), 0). - matched = search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) - if matched and not match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", matched.group(2)): - error(line_number, 'runtime/memset', 4, - 'Did you mean "memset(%s, 0, %s)"?' - % (matched.group(1), matched.group(2))) - - # Detect variable-length arrays. - matched = match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) - if (matched and matched.group(2) != 'return' and matched.group(2) != 'delete' and - matched.group(3).find(']') == -1): - # Split the size using space and arithmetic operators as delimiters. - # If any of the resulting tokens are not compile time constants then - # report the error. - tokens = re.split(r'\s|\+|\-|\*|\/|<<|>>]', matched.group(3)) - is_const = True - skip_next = False - for tok in tokens: - if skip_next: - skip_next = False - continue - - if search(r'sizeof\(.+\)', tok): - continue - if search(r'arraysize\(\w+\)', tok): - continue - - tok = tok.lstrip('(') - tok = tok.rstrip(')') - if not tok: - continue - if match(r'\d+', tok): - continue - if match(r'0[xX][0-9a-fA-F]+', tok): - continue - if match(r'k[A-Z0-9]\w*', tok): - continue - if match(r'(.+::)?k[A-Z0-9]\w*', tok): - continue - if match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): - continue - # A catch all for tricky sizeof cases, including 'sizeof expression', - # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' - # requires skipping the next token becasue we split on ' ' and '*'. - if tok.startswith('sizeof'): - skip_next = True - continue - is_const = False - break - if not is_const: - error(line_number, 'runtime/arrays', 1, - 'Do not use variable-length arrays. Use an appropriately named ' - "('k' followed by CamelCase) compile-time constant for the size.") - - # Check for use of unnamed namespaces in header files. Registration - # macros are typically OK, so we allow use of "namespace {" on lines - # that end with backslashes. - if (file_extension == 'h' - and search(r'\bnamespace\s*{', line) - and line[-1] != '\\'): - error(line_number, 'build/namespaces', 4, - 'Do not use unnamed namespaces in header files. See ' - 'http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' - ' for more information.') - - check_identifier_name_in_declaration(filename, line_number, line, error) - - -def check_identifier_name_in_declaration(filename, line_number, line, error): - """Checks if identifier names contain any underscores. - - As identifiers in libraries we are using have a bunch of - underscores, we only warn about the declarations of identifiers - and don't check use of identifiers. - - Args: - filename: The name of the current file. - line_number: The number of the line to check. - line: The line of code to check. - error: The function to call with any errors found. - """ - # We don't check a return statement. - if match(r'\s*(return|delete)\b', line): - return - - # Basically, a declaration is a type name followed by whitespaces - # followed by an identifier. The type name can be complicated - # due to type adjectives and templates. We remove them first to - # simplify the process to find declarations of identifiers. - - # Convert "long long", "long double", and "long long int" to - # simple types, but don't remove simple "long". - line = sub(r'long (long )?(?=long|double|int)', '', line) - # Convert unsigned/signed types to simple types, too. - line = sub(r'(unsigned|signed) (?=char|short|int|long)', '', line) - line = sub(r'\b(inline|using|static|const|volatile|auto|register|extern|typedef|restrict|struct|class|virtual)(?=\W)', '', line) - - # Remove "new" and "new (expr)" to simplify, too. - line = sub(r'new\s*(\([^)]*\))?', '', line) - - # Remove all template parameters by removing matching < and >. - # Loop until no templates are removed to remove nested templates. - while True: - line, number_of_replacements = subn(r'<([\w\s:]|::)+\s*[*&]*\s*>', '', line) - if not number_of_replacements: - break - - # Declarations of local variables can be in condition expressions - # of control flow statements (e.g., "if (RenderObject* p = o->parent())"). - # We remove the keywords and the first parenthesis. - # - # Declarations in "while", "if", and "switch" are different from - # other declarations in two aspects: - # - # - There can be only one declaration between the parentheses. - # (i.e., you cannot write "if (int i = 0, j = 1) {}") - # - The variable must be initialized. - # (i.e., you cannot write "if (int i) {}") - # - # and we will need different treatments for them. - line = sub(r'^\s*for\s*\(', '', line) - line, control_statement = subn(r'^\s*(while|else if|if|switch)\s*\(', '', line) - - # Detect variable and functions. - type_regexp = r'\w([\w]|\s*[*&]\s*|::)+' - identifier_regexp = r'(?P<identifier>[\w:]+)' - maybe_bitfield_regexp = r'(:\s*\d+\s*)?' - character_after_identifier_regexp = r'(?P<character_after_identifier>[[;()=,])(?!=)' - declaration_without_type_regexp = r'\s*' + identifier_regexp + r'\s*' + maybe_bitfield_regexp + character_after_identifier_regexp - declaration_with_type_regexp = r'\s*' + type_regexp + r'\s' + declaration_without_type_regexp - is_function_arguments = False - number_of_identifiers = 0 - while True: - # If we are seeing the first identifier or arguments of a - # function, there should be a type name before an identifier. - if not number_of_identifiers or is_function_arguments: - declaration_regexp = declaration_with_type_regexp - else: - declaration_regexp = declaration_without_type_regexp - - matched = match(declaration_regexp, line) - if not matched: - return - identifier = matched.group('identifier') - character_after_identifier = matched.group('character_after_identifier') - - # If we removed a non-for-control statement, the character after - # the identifier should be '='. With this rule, we can avoid - # warning for cases like "if (val & INT_MAX) {". - if control_statement and character_after_identifier != '=': - return - - is_function_arguments = is_function_arguments or character_after_identifier == '(' - - # Remove "m_" and "s_" to allow them. - modified_identifier = sub(r'(^|(?<=::))[ms]_', '', identifier) - if modified_identifier.find('_') >= 0: - # Various exceptions to the rule: JavaScript op codes functions, const_iterator. - if (not (filename.find('JavaScriptCore') >= 0 and modified_identifier.find('op_') >= 0) - and not modified_identifier.startswith('tst_') - and not modified_identifier.startswith('webkit_dom_object_') - and not modified_identifier.startswith('NPN_') - and not modified_identifier.startswith('NPP_') - and not modified_identifier.startswith('NP_') - and not modified_identifier.startswith('qt_') - and not modified_identifier.startswith('cairo_') - and not modified_identifier.find('::qt_') >= 0 - and not modified_identifier == "const_iterator" - and not modified_identifier == "vm_throw"): - error(line_number, 'readability/naming', 4, identifier + " is incorrectly named. Don't use underscores in your identifier names.") - - # Check for variables named 'l', these are too easy to confuse with '1' in some fonts - if modified_identifier == 'l': - error(line_number, 'readability/naming', 4, identifier + " is incorrectly named. Don't use the single letter 'l' as an identifier name.") - - # There can be only one declaration in non-for-control statements. - if control_statement: - return - # We should continue checking if this is a function - # declaration because we need to check its arguments. - # Also, we need to check multiple declarations. - if character_after_identifier != '(' and character_after_identifier != ',': - return - - number_of_identifiers += 1 - line = line[matched.end():] - -def check_c_style_cast(line_number, line, raw_line, cast_type, pattern, - error): - """Checks for a C-style cast by looking for the pattern. - - This also handles sizeof(type) warnings, due to similarity of content. - - Args: - line_number: The number of the line to check. - line: The line of code to check. - raw_line: The raw line of code to check, with comments. - cast_type: The string for the C++ cast to recommend. This is either - reinterpret_cast or static_cast, depending. - pattern: The regular expression used to find C-style casts. - error: The function to call with any errors found. - """ - matched = search(pattern, line) - if not matched: - return - - # e.g., sizeof(int) - sizeof_match = match(r'.*sizeof\s*$', line[0:matched.start(1) - 1]) - if sizeof_match: - error(line_number, 'runtime/sizeof', 1, - 'Using sizeof(type). Use sizeof(varname) instead if possible') - return - - remainder = line[matched.end(0):] - - # The close paren is for function pointers as arguments to a function. - # eg, void foo(void (*bar)(int)); - # The semicolon check is a more basic function check; also possibly a - # function pointer typedef. - # eg, void foo(int); or void foo(int) const; - # The equals check is for function pointer assignment. - # eg, void *(*foo)(int) = ... - # - # Right now, this will only catch cases where there's a single argument, and - # it's unnamed. It should probably be expanded to check for multiple - # arguments with some unnamed. - function_match = match(r'\s*(\)|=|(const)?\s*(;|\{|throw\(\)))', remainder) - if function_match: - if (not function_match.group(3) - or function_match.group(3) == ';' - or raw_line.find('/*') < 0): - error(line_number, 'readability/function', 3, - 'All parameters should be named in a function') - return - - # At this point, all that should be left is actual casts. - error(line_number, 'readability/casting', 4, - 'Using C-style cast. Use %s<%s>(...) instead' % - (cast_type, matched.group(1))) - - -_HEADERS_CONTAINING_TEMPLATES = ( - ('<deque>', ('deque',)), - ('<functional>', ('unary_function', 'binary_function', - 'plus', 'minus', 'multiplies', 'divides', 'modulus', - 'negate', - 'equal_to', 'not_equal_to', 'greater', 'less', - 'greater_equal', 'less_equal', - 'logical_and', 'logical_or', 'logical_not', - 'unary_negate', 'not1', 'binary_negate', 'not2', - 'bind1st', 'bind2nd', - 'pointer_to_unary_function', - 'pointer_to_binary_function', - 'ptr_fun', - 'mem_fun_t', 'mem_fun', 'mem_fun1_t', 'mem_fun1_ref_t', - 'mem_fun_ref_t', - 'const_mem_fun_t', 'const_mem_fun1_t', - 'const_mem_fun_ref_t', 'const_mem_fun1_ref_t', - 'mem_fun_ref', - )), - ('<limits>', ('numeric_limits',)), - ('<list>', ('list',)), - ('<map>', ('map', 'multimap',)), - ('<memory>', ('allocator',)), - ('<queue>', ('queue', 'priority_queue',)), - ('<set>', ('set', 'multiset',)), - ('<stack>', ('stack',)), - ('<string>', ('char_traits', 'basic_string',)), - ('<utility>', ('pair',)), - ('<vector>', ('vector',)), - - # gcc extensions. - # Note: std::hash is their hash, ::hash is our hash - ('<hash_map>', ('hash_map', 'hash_multimap',)), - ('<hash_set>', ('hash_set', 'hash_multiset',)), - ('<slist>', ('slist',)), - ) - -_HEADERS_ACCEPTED_BUT_NOT_PROMOTED = { - # We can trust with reasonable confidence that map gives us pair<>, too. - 'pair<>': ('map', 'multimap', 'hash_map', 'hash_multimap') -} - -_RE_PATTERN_STRING = re.compile(r'\bstring\b') - -_re_pattern_algorithm_header = [] -for _template in ('copy', 'max', 'min', 'min_element', 'sort', 'swap', - 'transform'): - # Match max<type>(..., ...), max(..., ...), but not foo->max, foo.max or - # type::max(). - _re_pattern_algorithm_header.append( - (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'), - _template, - '<algorithm>')) - -_re_pattern_templates = [] -for _header, _templates in _HEADERS_CONTAINING_TEMPLATES: - for _template in _templates: - _re_pattern_templates.append( - (re.compile(r'(\<|\b)' + _template + r'\s*\<'), - _template + '<>', - _header)) - - -def files_belong_to_same_module(filename_cpp, filename_h): - """Check if these two filenames belong to the same module. - - The concept of a 'module' here is a as follows: - foo.h, foo-inl.h, foo.cpp, foo_test.cpp and foo_unittest.cpp belong to the - same 'module' if they are in the same directory. - some/path/public/xyzzy and some/path/internal/xyzzy are also considered - to belong to the same module here. - - If the filename_cpp contains a longer path than the filename_h, for example, - '/absolute/path/to/base/sysinfo.cpp', and this file would include - 'base/sysinfo.h', this function also produces the prefix needed to open the - header. This is used by the caller of this function to more robustly open the - header file. We don't have access to the real include paths in this context, - so we need this guesswork here. - - Known bugs: tools/base/bar.cpp and base/bar.h belong to the same module - according to this implementation. Because of this, this function gives - some false positives. This should be sufficiently rare in practice. - - Args: - filename_cpp: is the path for the .cpp file - filename_h: is the path for the header path - - Returns: - Tuple with a bool and a string: - bool: True if filename_cpp and filename_h belong to the same module. - string: the additional prefix needed to open the header file. - """ - - if not filename_cpp.endswith('.cpp'): - return (False, '') - filename_cpp = filename_cpp[:-len('.cpp')] - if filename_cpp.endswith('_unittest'): - filename_cpp = filename_cpp[:-len('_unittest')] - elif filename_cpp.endswith('_test'): - filename_cpp = filename_cpp[:-len('_test')] - filename_cpp = filename_cpp.replace('/public/', '/') - filename_cpp = filename_cpp.replace('/internal/', '/') - - if not filename_h.endswith('.h'): - return (False, '') - filename_h = filename_h[:-len('.h')] - if filename_h.endswith('-inl'): - filename_h = filename_h[:-len('-inl')] - filename_h = filename_h.replace('/public/', '/') - filename_h = filename_h.replace('/internal/', '/') - - files_belong_to_same_module = filename_cpp.endswith(filename_h) - common_path = '' - if files_belong_to_same_module: - common_path = filename_cpp[:-len(filename_h)] - return files_belong_to_same_module, common_path - - -def update_include_state(filename, include_state, io=codecs): - """Fill up the include_state with new includes found from the file. - - Args: - filename: the name of the header to read. - include_state: an _IncludeState instance in which the headers are inserted. - io: The io factory to use to read the file. Provided for testability. - - Returns: - True if a header was succesfully added. False otherwise. - """ - header_file = None - try: - header_file = io.open(filename, 'r', 'utf8', 'replace') - except IOError: - return False - line_number = 0 - for line in header_file: - line_number += 1 - clean_line = cleanse_comments(line) - matched = _RE_PATTERN_INCLUDE.search(clean_line) - if matched: - include = matched.group(2) - # The value formatting is cute, but not really used right now. - # What matters here is that the key is in include_state. - include_state.setdefault(include, '%s:%d' % (filename, line_number)) - return True - - -def check_for_include_what_you_use(filename, clean_lines, include_state, error, - io=codecs): - """Reports for missing stl includes. - - This function will output warnings to make sure you are including the headers - necessary for the stl containers and functions that you use. We only give one - reason to include a header. For example, if you use both equal_to<> and - less<> in a .h file, only one (the latter in the file) of these will be - reported as a reason to include the <functional>. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - include_state: An _IncludeState instance. - error: The function to call with any errors found. - io: The IO factory to use to read the header file. Provided for unittest - injection. - """ - required = {} # A map of header name to line_number and the template entity. - # Example of required: { '<functional>': (1219, 'less<>') } - - for line_number in xrange(clean_lines.num_lines()): - line = clean_lines.elided[line_number] - if not line or line[0] == '#': - continue - - # String is special -- it is a non-templatized type in STL. - if _RE_PATTERN_STRING.search(line): - required['<string>'] = (line_number, 'string') - - for pattern, template, header in _re_pattern_algorithm_header: - if pattern.search(line): - required[header] = (line_number, template) - - # The following function is just a speed up, no semantics are changed. - if not '<' in line: # Reduces the cpu time usage by skipping lines. - continue - - for pattern, template, header in _re_pattern_templates: - if pattern.search(line): - required[header] = (line_number, template) - - # The policy is that if you #include something in foo.h you don't need to - # include it again in foo.cpp. Here, we will look at possible includes. - # Let's copy the include_state so it is only messed up within this function. - include_state = include_state.copy() - - # Did we find the header for this file (if any) and succesfully load it? - header_found = False - - # Use the absolute path so that matching works properly. - abs_filename = os.path.abspath(filename) - - # For Emacs's flymake. - # If cpp_style is invoked from Emacs's flymake, a temporary file is generated - # by flymake and that file name might end with '_flymake.cpp'. In that case, - # restore original file name here so that the corresponding header file can be - # found. - # e.g. If the file name is 'foo_flymake.cpp', we should search for 'foo.h' - # instead of 'foo_flymake.h' - abs_filename = re.sub(r'_flymake\.cpp$', '.cpp', abs_filename) - - # include_state is modified during iteration, so we iterate over a copy of - # the keys. - for header in include_state.keys(): #NOLINT - (same_module, common_path) = files_belong_to_same_module(abs_filename, header) - fullpath = common_path + header - if same_module and update_include_state(fullpath, include_state, io): - header_found = True - - # If we can't find the header file for a .cpp, assume it's because we don't - # know where to look. In that case we'll give up as we're not sure they - # didn't include it in the .h file. - # FIXME: Do a better job of finding .h files so we are confident that - # not having the .h file means there isn't one. - if filename.endswith('.cpp') and not header_found: - return - - # All the lines have been processed, report the errors found. - for required_header_unstripped in required: - template = required[required_header_unstripped][1] - if template in _HEADERS_ACCEPTED_BUT_NOT_PROMOTED: - headers = _HEADERS_ACCEPTED_BUT_NOT_PROMOTED[template] - if [True for header in headers if header in include_state]: - continue - if required_header_unstripped.strip('<>"') not in include_state: - error(required[required_header_unstripped][0], - 'build/include_what_you_use', 4, - 'Add #include ' + required_header_unstripped + ' for ' + template) - - -def process_line(filename, file_extension, - clean_lines, line, include_state, function_state, - class_state, file_state, error): - """Processes a single line in the file. - - Args: - filename: Filename of the file that is being processed. - file_extension: The extension (dot not included) of the file. - clean_lines: An array of strings, each representing a line of the file, - with comments stripped. - line: Number of line being processed. - include_state: An _IncludeState instance in which the headers are inserted. - function_state: A _FunctionState instance which counts function lines, etc. - class_state: A _ClassState instance which maintains information about - the current stack of nested class declarations being parsed. - file_state: A _FileState instance which maintains information about - the state of things in the file. - error: A callable to which errors are reported, which takes arguments: - line number, error level, and message - - """ - raw_lines = clean_lines.raw_lines - detect_functions(clean_lines, line, function_state, error) - check_for_function_lengths(clean_lines, line, function_state, error) - if search(r'\bNOLINT\b', raw_lines[line]): # ignore nolint lines - return - check_pass_ptr_usage(clean_lines, line, function_state, error) - check_for_multiline_comments_and_strings(clean_lines, line, error) - check_style(clean_lines, line, file_extension, class_state, file_state, error) - check_language(filename, clean_lines, line, file_extension, include_state, - error) - check_for_non_standard_constructs(clean_lines, line, class_state, error) - check_posix_threading(clean_lines, line, error) - check_invalid_increment(clean_lines, line, error) - - -def _process_lines(filename, file_extension, lines, error, min_confidence): - """Performs lint checks and reports any errors to the given error function. - - Args: - filename: Filename of the file that is being processed. - file_extension: The extension (dot not included) of the file. - lines: An array of strings, each representing a line of the file, with the - last element being empty if the file is termined with a newline. - error: A callable to which errors are reported, which takes 4 arguments: - """ - lines = (['// marker so line numbers and indices both start at 1'] + lines + - ['// marker so line numbers end in a known way']) - - include_state = _IncludeState() - function_state = _FunctionState(min_confidence) - class_state = _ClassState() - file_state = _FileState() - - check_for_copyright(lines, error) - - if file_extension == 'h': - check_for_header_guard(filename, lines, error) - - remove_multi_line_comments(lines, error) - clean_lines = CleansedLines(lines) - for line in xrange(clean_lines.num_lines()): - process_line(filename, file_extension, clean_lines, line, - include_state, function_state, class_state, file_state, error) - class_state.check_finished(error) - - check_for_include_what_you_use(filename, clean_lines, include_state, error) - - # We check here rather than inside process_line so that we see raw - # lines rather than "cleaned" lines. - check_for_unicode_replacement_characters(lines, error) - - check_for_new_line_at_eof(lines, error) - - -class CppChecker(object): - - """Processes C++ lines for checking style.""" - - # This list is used to-- - # - # (1) generate an explicit list of all possible categories, - # (2) unit test that all checked categories have valid names, and - # (3) unit test that all categories are getting unit tested. - # - categories = set([ - 'build/class', - 'build/deprecated', - 'build/endif_comment', - 'build/forward_decl', - 'build/header_guard', - 'build/include', - 'build/include_order', - 'build/include_what_you_use', - 'build/namespaces', - 'build/printf_format', - 'build/storage_class', - 'build/using_std', - 'legal/copyright', - 'readability/braces', - 'readability/casting', - 'readability/check', - 'readability/comparison_to_zero', - 'readability/constructors', - 'readability/control_flow', - 'readability/fn_size', - 'readability/function', - 'readability/multiline_comment', - 'readability/multiline_string', - 'readability/naming', - 'readability/null', - 'readability/pass_ptr', - 'readability/streams', - 'readability/todo', - 'readability/utf8', - 'runtime/arrays', - 'runtime/casting', - 'runtime/explicit', - 'runtime/init', - 'runtime/int', - 'runtime/invalid_increment', - 'runtime/max_min_macros', - 'runtime/memset', - 'runtime/printf', - 'runtime/printf_format', - 'runtime/references', - 'runtime/rtti', - 'runtime/sizeof', - 'runtime/string', - 'runtime/threadsafe_fn', - 'runtime/virtual', - 'whitespace/blank_line', - 'whitespace/braces', - 'whitespace/comma', - 'whitespace/comments', - 'whitespace/declaration', - 'whitespace/end_of_line', - 'whitespace/ending_newline', - 'whitespace/indent', - 'whitespace/labels', - 'whitespace/line_length', - 'whitespace/newline', - 'whitespace/operators', - 'whitespace/parens', - 'whitespace/semicolon', - 'whitespace/tab', - 'whitespace/todo', - ]) - - def __init__(self, file_path, file_extension, handle_style_error, - min_confidence): - """Create a CppChecker instance. - - Args: - file_extension: A string that is the file extension, without - the leading dot. - - """ - self.file_extension = file_extension - self.file_path = file_path - self.handle_style_error = handle_style_error - self.min_confidence = min_confidence - - # Useful for unit testing. - def __eq__(self, other): - """Return whether this CppChecker instance is equal to another.""" - if self.file_extension != other.file_extension: - return False - if self.file_path != other.file_path: - return False - if self.handle_style_error != other.handle_style_error: - return False - if self.min_confidence != other.min_confidence: - return False - - return True - - # Useful for unit testing. - def __ne__(self, other): - # Python does not automatically deduce __ne__() from __eq__(). - return not self.__eq__(other) - - def check(self, lines): - _process_lines(self.file_path, self.file_extension, lines, - self.handle_style_error, self.min_confidence) - - -# FIXME: Remove this function (requires refactoring unit tests). -def process_file_data(filename, file_extension, lines, error, min_confidence): - checker = CppChecker(filename, file_extension, error, min_confidence) - checker.check(lines) diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/cpp_unittest.py b/WebKitTools/Scripts/webkitpy/style/checkers/cpp_unittest.py deleted file mode 100644 index 13b053c..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/cpp_unittest.py +++ /dev/null @@ -1,3923 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; -*- -# -# Copyright (C) 2009 Google Inc. All rights reserved. -# Copyright (C) 2009 Torch Mobile Inc. -# Copyright (C) 2009 Apple Inc. 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 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 test for cpp_style.py.""" - -# FIXME: Add a good test that tests UpdateIncludeState. - -import codecs -import os -import random -import re -import unittest -import cpp as cpp_style -from cpp import CppChecker - -# This class works as an error collector and replaces cpp_style.Error -# function for the unit tests. We also verify each category we see -# is in STYLE_CATEGORIES, to help keep that list up to date. -class ErrorCollector: - _all_style_categories = CppChecker.categories - # This is a list including all categories seen in any unit test. - _seen_style_categories = {} - - def __init__(self, assert_fn): - """assert_fn: a function to call when we notice a problem.""" - self._assert_fn = assert_fn - self._errors = [] - - def __call__(self, unused_linenum, category, confidence, message): - self._assert_fn(category in self._all_style_categories, - 'Message "%s" has category "%s",' - ' which is not in STYLE_CATEGORIES' % (message, category)) - self._seen_style_categories[category] = 1 - self._errors.append('%s [%s] [%d]' % (message, category, confidence)) - - def results(self): - if len(self._errors) < 2: - return ''.join(self._errors) # Most tests expect to have a string. - else: - return self._errors # Let's give a list if there is more than one. - - def result_list(self): - return self._errors - - def verify_all_categories_are_seen(self): - """Fails if there's a category in _all_style_categories - _seen_style_categories. - - This should only be called after all tests are run, so - _seen_style_categories has had a chance to fully populate. Since - this isn't called from within the normal unittest framework, we - can't use the normal unittest assert macros. Instead we just exit - when we see an error. Good thing this test is always run last! - """ - for category in self._all_style_categories: - if category not in self._seen_style_categories: - import sys - sys.exit('FATAL ERROR: There are no tests for category "%s"' % category) - - def remove_if_present(self, substr): - for (index, error) in enumerate(self._errors): - if error.find(substr) != -1: - self._errors = self._errors[0:index] + self._errors[(index + 1):] - break - - -# This class is a lame mock of codecs. We do not verify filename, mode, or -# encoding, but for the current use case it is not needed. -class MockIo: - def __init__(self, mock_file): - self.mock_file = mock_file - - def open(self, unused_filename, unused_mode, unused_encoding, _): # NOLINT - # (lint doesn't like open as a method name) - return self.mock_file - - -class CppFunctionsTest(unittest.TestCase): - - """Supports testing functions that do not need CppStyleTestBase.""" - - def test_is_c_or_objective_c(self): - self.assertTrue(cpp_style.is_c_or_objective_c("c")) - self.assertTrue(cpp_style.is_c_or_objective_c("m")) - self.assertFalse(cpp_style.is_c_or_objective_c("cpp")) - - -class CppStyleTestBase(unittest.TestCase): - """Provides some useful helper functions for cpp_style tests. - - Attributes: - min_confidence: An integer that is the current minimum confidence - level for the tests. - - """ - - # FIXME: Refactor the unit tests so the confidence level is passed - # explicitly, just like it is in the real code. - min_confidence = 1; - - # Helper function to avoid needing to explicitly pass confidence - # in all the unit test calls to cpp_style.process_file_data(). - def process_file_data(self, filename, file_extension, lines, error): - """Call cpp_style.process_file_data() with the min_confidence.""" - return cpp_style.process_file_data(filename, file_extension, lines, - error, self.min_confidence) - - # Perform lint on single line of input and return the error message. - def perform_single_line_lint(self, code, file_name): - error_collector = ErrorCollector(self.assert_) - lines = code.split('\n') - cpp_style.remove_multi_line_comments(lines, error_collector) - clean_lines = cpp_style.CleansedLines(lines) - include_state = cpp_style._IncludeState() - function_state = cpp_style._FunctionState(self.min_confidence) - ext = file_name[file_name.rfind('.') + 1:] - class_state = cpp_style._ClassState() - file_state = cpp_style._FileState() - cpp_style.process_line(file_name, ext, clean_lines, 0, - include_state, function_state, - class_state, file_state, error_collector) - # Single-line lint tests are allowed to fail the 'unlintable function' - # check. - error_collector.remove_if_present( - 'Lint failed to find start of function body.') - return error_collector.results() - - # Perform lint over multiple lines and return the error message. - def perform_multi_line_lint(self, code, file_extension): - error_collector = ErrorCollector(self.assert_) - lines = code.split('\n') - cpp_style.remove_multi_line_comments(lines, error_collector) - lines = cpp_style.CleansedLines(lines) - class_state = cpp_style._ClassState() - file_state = cpp_style._FileState() - for i in xrange(lines.num_lines()): - cpp_style.check_style(lines, i, file_extension, class_state, file_state, error_collector) - cpp_style.check_for_non_standard_constructs(lines, i, class_state, - error_collector) - class_state.check_finished(error_collector) - return error_collector.results() - - # Similar to perform_multi_line_lint, but calls check_language instead of - # check_for_non_standard_constructs - def perform_language_rules_check(self, file_name, code): - error_collector = ErrorCollector(self.assert_) - include_state = cpp_style._IncludeState() - lines = code.split('\n') - cpp_style.remove_multi_line_comments(lines, error_collector) - lines = cpp_style.CleansedLines(lines) - ext = file_name[file_name.rfind('.') + 1:] - for i in xrange(lines.num_lines()): - cpp_style.check_language(file_name, lines, i, ext, include_state, - error_collector) - return error_collector.results() - - def perform_function_lengths_check(self, code): - """Perform Lint function length check on block of code and return warnings. - - Builds up an array of lines corresponding to the code and strips comments - using cpp_style functions. - - Establishes an error collector and invokes the function length checking - function following cpp_style's pattern. - - Args: - code: C++ source code expected to generate a warning message. - - Returns: - The accumulated errors. - """ - error_collector = ErrorCollector(self.assert_) - function_state = cpp_style._FunctionState(self.min_confidence) - lines = code.split('\n') - cpp_style.remove_multi_line_comments(lines, error_collector) - lines = cpp_style.CleansedLines(lines) - for i in xrange(lines.num_lines()): - cpp_style.detect_functions(lines, i, - function_state, error_collector) - cpp_style.check_for_function_lengths(lines, i, - function_state, error_collector) - return error_collector.results() - - # Similar to perform_function_lengths_check, but calls check_pass_ptr_usage - # instead of check_for_function_lengths. - def perform_pass_ptr_check(self, code): - error_collector = ErrorCollector(self.assert_) - function_state = cpp_style._FunctionState(self.min_confidence) - lines = code.split('\n') - cpp_style.remove_multi_line_comments(lines, error_collector) - lines = cpp_style.CleansedLines(lines) - for i in xrange(lines.num_lines()): - cpp_style.detect_functions(lines, i, - function_state, error_collector) - cpp_style.check_pass_ptr_usage(lines, i, - function_state, error_collector) - return error_collector.results() - - def perform_include_what_you_use(self, code, filename='foo.h', io=codecs): - # First, build up the include state. - error_collector = ErrorCollector(self.assert_) - include_state = cpp_style._IncludeState() - lines = code.split('\n') - cpp_style.remove_multi_line_comments(lines, error_collector) - lines = cpp_style.CleansedLines(lines) - file_extension = filename[filename.rfind('.') + 1:] - for i in xrange(lines.num_lines()): - cpp_style.check_language(filename, lines, i, file_extension, include_state, - error_collector) - # We could clear the error_collector here, but this should - # also be fine, since our IncludeWhatYouUse unittests do not - # have language problems. - - # Second, look for missing includes. - cpp_style.check_for_include_what_you_use(filename, lines, include_state, - error_collector, io) - return error_collector.results() - - # Perform lint and compare the error message with "expected_message". - def assert_lint(self, code, expected_message, file_name='foo.cpp'): - self.assertEquals(expected_message, self.perform_single_line_lint(code, file_name)) - - def assert_lint_one_of_many_errors_re(self, code, expected_message_re, file_name='foo.cpp'): - messages = self.perform_single_line_lint(code, file_name) - for message in messages: - if re.search(expected_message_re, message): - return - - self.assertEquals(expected_message_re, messages) - - def assert_multi_line_lint(self, code, expected_message, file_name='foo.h'): - file_extension = file_name[file_name.rfind('.') + 1:] - self.assertEquals(expected_message, self.perform_multi_line_lint(code, file_extension)) - - def assert_multi_line_lint_re(self, code, expected_message_re, file_name='foo.h'): - file_extension = file_name[file_name.rfind('.') + 1:] - message = self.perform_multi_line_lint(code, file_extension) - if not re.search(expected_message_re, message): - self.fail('Message was:\n' + message + 'Expected match to "' + expected_message_re + '"') - - def assert_language_rules_check(self, file_name, code, expected_message): - self.assertEquals(expected_message, - self.perform_language_rules_check(file_name, code)) - - def assert_include_what_you_use(self, code, expected_message): - self.assertEquals(expected_message, - self.perform_include_what_you_use(code)) - - def assert_blank_lines_check(self, lines, start_errors, end_errors): - error_collector = ErrorCollector(self.assert_) - self.process_file_data('foo.cpp', 'cpp', lines, error_collector) - self.assertEquals( - start_errors, - error_collector.results().count( - 'Blank line at the start of a code block. Is this needed?' - ' [whitespace/blank_line] [2]')) - self.assertEquals( - end_errors, - error_collector.results().count( - 'Blank line at the end of a code block. Is this needed?' - ' [whitespace/blank_line] [3]')) - - -class CppStyleTest(CppStyleTestBase): - - # Test get line width. - def test_get_line_width(self): - self.assertEquals(0, cpp_style.get_line_width('')) - self.assertEquals(10, cpp_style.get_line_width(u'x' * 10)) - self.assertEquals(16, cpp_style.get_line_width(u'都|道|府|県|支庁')) - - def test_find_next_multi_line_comment_start(self): - self.assertEquals(1, cpp_style.find_next_multi_line_comment_start([''], 0)) - - lines = ['a', 'b', '/* c'] - self.assertEquals(2, cpp_style.find_next_multi_line_comment_start(lines, 0)) - - lines = ['char a[] = "/*";'] # not recognized as comment. - self.assertEquals(1, cpp_style.find_next_multi_line_comment_start(lines, 0)) - - def test_find_next_multi_line_comment_end(self): - self.assertEquals(1, cpp_style.find_next_multi_line_comment_end([''], 0)) - lines = ['a', 'b', ' c */'] - self.assertEquals(2, cpp_style.find_next_multi_line_comment_end(lines, 0)) - - def test_remove_multi_line_comments_from_range(self): - lines = ['a', ' /* comment ', ' * still comment', ' comment */ ', 'b'] - cpp_style.remove_multi_line_comments_from_range(lines, 1, 4) - self.assertEquals(['a', '// dummy', '// dummy', '// dummy', 'b'], lines) - - def test_spaces_at_end_of_line(self): - self.assert_lint( - '// Hello there ', - 'Line ends in whitespace. Consider deleting these extra spaces.' - ' [whitespace/end_of_line] [4]') - - # Test C-style cast cases. - def test_cstyle_cast(self): - self.assert_lint( - 'int a = (int)1.0;', - 'Using C-style cast. Use static_cast<int>(...) instead' - ' [readability/casting] [4]') - self.assert_lint( - 'int *a = (int *)DEFINED_VALUE;', - 'Using C-style cast. Use reinterpret_cast<int *>(...) instead' - ' [readability/casting] [4]', 'foo.c') - self.assert_lint( - 'uint16 a = (uint16)1.0;', - 'Using C-style cast. Use static_cast<uint16>(...) instead' - ' [readability/casting] [4]') - self.assert_lint( - 'int32 a = (int32)1.0;', - 'Using C-style cast. Use static_cast<int32>(...) instead' - ' [readability/casting] [4]') - self.assert_lint( - 'uint64 a = (uint64)1.0;', - 'Using C-style cast. Use static_cast<uint64>(...) instead' - ' [readability/casting] [4]') - - # Test taking address of casts (runtime/casting) - def test_runtime_casting(self): - self.assert_lint( - 'int* x = &static_cast<int*>(foo);', - 'Are you taking an address of a cast? ' - 'This is dangerous: could be a temp var. ' - 'Take the address before doing the cast, rather than after' - ' [runtime/casting] [4]') - - self.assert_lint( - 'int* x = &dynamic_cast<int *>(foo);', - ['Are you taking an address of a cast? ' - 'This is dangerous: could be a temp var. ' - 'Take the address before doing the cast, rather than after' - ' [runtime/casting] [4]', - 'Do not use dynamic_cast<>. If you need to cast within a class ' - 'hierarchy, use static_cast<> to upcast. Google doesn\'t support ' - 'RTTI. [runtime/rtti] [5]']) - - self.assert_lint( - 'int* x = &reinterpret_cast<int *>(foo);', - 'Are you taking an address of a cast? ' - 'This is dangerous: could be a temp var. ' - 'Take the address before doing the cast, rather than after' - ' [runtime/casting] [4]') - - # It's OK to cast an address. - self.assert_lint( - 'int* x = reinterpret_cast<int *>(&foo);', - '') - - def test_runtime_selfinit(self): - self.assert_lint( - 'Foo::Foo(Bar r, Bel l) : r_(r_), l_(l_) { }', - 'You seem to be initializing a member variable with itself.' - ' [runtime/init] [4]') - self.assert_lint( - 'Foo::Foo(Bar r, Bel l) : r_(r), l_(l) { }', - '') - self.assert_lint( - 'Foo::Foo(Bar r) : r_(r), l_(r_), ll_(l_) { }', - '') - - def test_runtime_rtti(self): - statement = 'int* x = dynamic_cast<int*>(&foo);' - error_message = ( - 'Do not use dynamic_cast<>. If you need to cast within a class ' - 'hierarchy, use static_cast<> to upcast. Google doesn\'t support ' - 'RTTI. [runtime/rtti] [5]') - # dynamic_cast is disallowed in most files. - self.assert_language_rules_check('foo.cpp', statement, error_message) - self.assert_language_rules_check('foo.h', statement, error_message) - - # We cannot test this functionality because of difference of - # function definitions. Anyway, we may never enable this. - # - # # Test for unnamed arguments in a method. - # def test_check_for_unnamed_params(self): - # message = ('All parameters should be named in a function' - # ' [readability/function] [3]') - # self.assert_lint('virtual void A(int*) const;', message) - # self.assert_lint('virtual void B(void (*fn)(int*));', message) - # self.assert_lint('virtual void C(int*);', message) - # self.assert_lint('void *(*f)(void *) = x;', message) - # self.assert_lint('void Method(char*) {', message) - # self.assert_lint('void Method(char*);', message) - # self.assert_lint('void Method(char* /*x*/);', message) - # self.assert_lint('typedef void (*Method)(int32);', message) - # self.assert_lint('static void operator delete[](void*) throw();', message) - # - # self.assert_lint('virtual void D(int* p);', '') - # self.assert_lint('void operator delete(void* x) throw();', '') - # self.assert_lint('void Method(char* x)\n{', '') - # self.assert_lint('void Method(char* /*x*/)\n{', '') - # self.assert_lint('void Method(char* x);', '') - # self.assert_lint('typedef void (*Method)(int32 x);', '') - # self.assert_lint('static void operator delete[](void* x) throw();', '') - # self.assert_lint('static void operator delete[](void* /*x*/) throw();', '') - # - # # This one should technically warn, but doesn't because the function - # # pointer is confusing. - # self.assert_lint('virtual void E(void (*fn)(int* p));', '') - - # Test deprecated casts such as int(d) - def test_deprecated_cast(self): - self.assert_lint( - 'int a = int(2.2);', - 'Using deprecated casting style. ' - 'Use static_cast<int>(...) instead' - ' [readability/casting] [4]') - # Checks for false positives... - self.assert_lint( - 'int a = int(); // Constructor, o.k.', - '') - self.assert_lint( - 'X::X() : a(int()) {} // default Constructor, o.k.', - '') - self.assert_lint( - 'operator bool(); // Conversion operator, o.k.', - '') - - # The second parameter to a gMock method definition is a function signature - # that often looks like a bad cast but should not picked up by lint. - def test_mock_method(self): - self.assert_lint( - 'MOCK_METHOD0(method, int());', - '') - self.assert_lint( - 'MOCK_CONST_METHOD1(method, float(string));', - '') - self.assert_lint( - 'MOCK_CONST_METHOD2_T(method, double(float, float));', - '') - - # Test sizeof(type) cases. - def test_sizeof_type(self): - self.assert_lint( - 'sizeof(int);', - 'Using sizeof(type). Use sizeof(varname) instead if possible' - ' [runtime/sizeof] [1]') - self.assert_lint( - 'sizeof(int *);', - 'Using sizeof(type). Use sizeof(varname) instead if possible' - ' [runtime/sizeof] [1]') - - # Test typedef cases. There was a bug that cpp_style misidentified - # typedef for pointer to function as C-style cast and produced - # false-positive error messages. - def test_typedef_for_pointer_to_function(self): - self.assert_lint( - 'typedef void (*Func)(int x);', - '') - self.assert_lint( - 'typedef void (*Func)(int *x);', - '') - self.assert_lint( - 'typedef void Func(int x);', - '') - self.assert_lint( - 'typedef void Func(int *x);', - '') - - def test_include_what_you_use_no_implementation_files(self): - code = 'std::vector<int> foo;' - self.assertEquals('Add #include <vector> for vector<>' - ' [build/include_what_you_use] [4]', - self.perform_include_what_you_use(code, 'foo.h')) - self.assertEquals('', - self.perform_include_what_you_use(code, 'foo.cpp')) - - def test_include_what_you_use(self): - self.assert_include_what_you_use( - '''#include <vector> - std::vector<int> foo; - ''', - '') - self.assert_include_what_you_use( - '''#include <map> - std::pair<int,int> foo; - ''', - '') - self.assert_include_what_you_use( - '''#include <multimap> - std::pair<int,int> foo; - ''', - '') - self.assert_include_what_you_use( - '''#include <hash_map> - std::pair<int,int> foo; - ''', - '') - self.assert_include_what_you_use( - '''#include <utility> - std::pair<int,int> foo; - ''', - '') - self.assert_include_what_you_use( - '''#include <vector> - DECLARE_string(foobar); - ''', - '') - self.assert_include_what_you_use( - '''#include <vector> - DEFINE_string(foobar, "", ""); - ''', - '') - self.assert_include_what_you_use( - '''#include <vector> - std::pair<int,int> foo; - ''', - 'Add #include <utility> for pair<>' - ' [build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''#include "base/foobar.h" - std::vector<int> foo; - ''', - 'Add #include <vector> for vector<>' - ' [build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''#include <vector> - std::set<int> foo; - ''', - 'Add #include <set> for set<>' - ' [build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''#include "base/foobar.h" - hash_map<int, int> foobar; - ''', - 'Add #include <hash_map> for hash_map<>' - ' [build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''#include "base/foobar.h" - bool foobar = std::less<int>(0,1); - ''', - 'Add #include <functional> for less<>' - ' [build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''#include "base/foobar.h" - bool foobar = min<int>(0,1); - ''', - 'Add #include <algorithm> for min [build/include_what_you_use] [4]') - self.assert_include_what_you_use( - 'void a(const string &foobar);', - 'Add #include <string> for string [build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''#include "base/foobar.h" - bool foobar = swap(0,1); - ''', - 'Add #include <algorithm> for swap [build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''#include "base/foobar.h" - bool foobar = transform(a.begin(), a.end(), b.start(), Foo); - ''', - 'Add #include <algorithm> for transform ' - '[build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''#include "base/foobar.h" - bool foobar = min_element(a.begin(), a.end()); - ''', - 'Add #include <algorithm> for min_element ' - '[build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''foo->swap(0,1); - foo.swap(0,1); - ''', - '') - self.assert_include_what_you_use( - '''#include <string> - void a(const std::multimap<int,string> &foobar); - ''', - 'Add #include <map> for multimap<>' - ' [build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''#include <queue> - void a(const std::priority_queue<int> &foobar); - ''', - '') - self.assert_include_what_you_use( - '''#include "base/basictypes.h" - #include "base/port.h" - #include <assert.h> - #include <string> - #include <vector> - vector<string> hajoa;''', '') - self.assert_include_what_you_use( - '''#include <string> - int i = numeric_limits<int>::max() - ''', - 'Add #include <limits> for numeric_limits<>' - ' [build/include_what_you_use] [4]') - self.assert_include_what_you_use( - '''#include <limits> - int i = numeric_limits<int>::max() - ''', - '') - - # Test the UpdateIncludeState code path. - mock_header_contents = ['#include "blah/foo.h"', '#include "blah/bar.h"'] - message = self.perform_include_what_you_use( - '#include "config.h"\n' - '#include "blah/a.h"\n', - filename='blah/a.cpp', - io=MockIo(mock_header_contents)) - self.assertEquals(message, '') - - mock_header_contents = ['#include <set>'] - message = self.perform_include_what_you_use( - '''#include "config.h" - #include "blah/a.h" - - std::set<int> foo;''', - filename='blah/a.cpp', - io=MockIo(mock_header_contents)) - self.assertEquals(message, '') - - # If there's just a .cpp and the header can't be found then it's ok. - message = self.perform_include_what_you_use( - '''#include "config.h" - #include "blah/a.h" - - std::set<int> foo;''', - filename='blah/a.cpp') - self.assertEquals(message, '') - - # Make sure we find the headers with relative paths. - mock_header_contents = [''] - message = self.perform_include_what_you_use( - '''#include "config.h" - #include "%s/a.h" - - std::set<int> foo;''' % os.path.basename(os.getcwd()), - filename='a.cpp', - io=MockIo(mock_header_contents)) - self.assertEquals(message, 'Add #include <set> for set<> ' - '[build/include_what_you_use] [4]') - - def test_files_belong_to_same_module(self): - f = cpp_style.files_belong_to_same_module - self.assertEquals((True, ''), f('a.cpp', 'a.h')) - self.assertEquals((True, ''), f('base/google.cpp', 'base/google.h')) - self.assertEquals((True, ''), f('base/google_test.cpp', 'base/google.h')) - self.assertEquals((True, ''), - f('base/google_unittest.cpp', 'base/google.h')) - self.assertEquals((True, ''), - f('base/internal/google_unittest.cpp', - 'base/public/google.h')) - self.assertEquals((True, 'xxx/yyy/'), - f('xxx/yyy/base/internal/google_unittest.cpp', - 'base/public/google.h')) - self.assertEquals((True, 'xxx/yyy/'), - f('xxx/yyy/base/google_unittest.cpp', - 'base/public/google.h')) - self.assertEquals((True, ''), - f('base/google_unittest.cpp', 'base/google-inl.h')) - self.assertEquals((True, '/home/build/google3/'), - f('/home/build/google3/base/google.cpp', 'base/google.h')) - - self.assertEquals((False, ''), - f('/home/build/google3/base/google.cpp', 'basu/google.h')) - self.assertEquals((False, ''), f('a.cpp', 'b.h')) - - def test_cleanse_line(self): - self.assertEquals('int foo = 0; ', - cpp_style.cleanse_comments('int foo = 0; // danger!')) - self.assertEquals('int o = 0;', - cpp_style.cleanse_comments('int /* foo */ o = 0;')) - self.assertEquals('foo(int a, int b);', - cpp_style.cleanse_comments('foo(int a /* abc */, int b);')) - self.assertEqual('f(a, b);', - cpp_style.cleanse_comments('f(a, /* name */ b);')) - self.assertEqual('f(a, b);', - cpp_style.cleanse_comments('f(a /* name */, b);')) - self.assertEqual('f(a, b);', - cpp_style.cleanse_comments('f(a, /* name */b);')) - - def test_multi_line_comments(self): - # missing explicit is bad - self.assert_multi_line_lint( - r'''int a = 0; - /* multi-liner - class Foo { - Foo(int f); // should cause a lint warning in code - } - */ ''', - '') - self.assert_multi_line_lint( - r'''/* int a = 0; multi-liner - static const int b = 0;''', - 'Could not find end of multi-line comment' - ' [readability/multiline_comment] [5]') - self.assert_multi_line_lint(r''' /* multi-line comment''', - 'Could not find end of multi-line comment' - ' [readability/multiline_comment] [5]') - self.assert_multi_line_lint(r''' // /* comment, but not multi-line''', '') - - def test_multiline_strings(self): - multiline_string_error_message = ( - 'Multi-line string ("...") found. This lint script doesn\'t ' - 'do well with such strings, and may give bogus warnings. They\'re ' - 'ugly and unnecessary, and you should use concatenation instead".' - ' [readability/multiline_string] [5]') - - file_path = 'mydir/foo.cpp' - - error_collector = ErrorCollector(self.assert_) - self.process_file_data(file_path, 'cpp', - ['const char* str = "This is a\\', - ' multiline string.";'], - error_collector) - self.assertEquals( - 2, # One per line. - error_collector.result_list().count(multiline_string_error_message)) - - # Test non-explicit single-argument constructors - def test_explicit_single_argument_constructors(self): - # missing explicit is bad - self.assert_multi_line_lint( - '''class Foo { - Foo(int f); - };''', - 'Single-argument constructors should be marked explicit.' - ' [runtime/explicit] [5]') - # missing explicit is bad, even with whitespace - self.assert_multi_line_lint( - '''class Foo { - Foo (int f); - };''', - ['Extra space before ( in function call [whitespace/parens] [4]', - 'Single-argument constructors should be marked explicit.' - ' [runtime/explicit] [5]']) - # missing explicit, with distracting comment, is still bad - self.assert_multi_line_lint( - '''class Foo { - Foo(int f); // simpler than Foo(blargh, blarg) - };''', - 'Single-argument constructors should be marked explicit.' - ' [runtime/explicit] [5]') - # missing explicit, with qualified classname - self.assert_multi_line_lint( - '''class Qualifier::AnotherOne::Foo { - Foo(int f); - };''', - 'Single-argument constructors should be marked explicit.' - ' [runtime/explicit] [5]') - # structs are caught as well. - self.assert_multi_line_lint( - '''struct Foo { - Foo(int f); - };''', - 'Single-argument constructors should be marked explicit.' - ' [runtime/explicit] [5]') - # Templatized classes are caught as well. - self.assert_multi_line_lint( - '''template<typename T> class Foo { - Foo(int f); - };''', - 'Single-argument constructors should be marked explicit.' - ' [runtime/explicit] [5]') - # proper style is okay - self.assert_multi_line_lint( - '''class Foo { - explicit Foo(int f); - };''', - '') - # two argument constructor is okay - self.assert_multi_line_lint( - '''class Foo { - Foo(int f, int b); - };''', - '') - # two argument constructor, across two lines, is okay - self.assert_multi_line_lint( - '''class Foo { - Foo(int f, - int b); - };''', - '') - # non-constructor (but similar name), is okay - self.assert_multi_line_lint( - '''class Foo { - aFoo(int f); - };''', - '') - # constructor with void argument is okay - self.assert_multi_line_lint( - '''class Foo { - Foo(void); - };''', - '') - # single argument method is okay - self.assert_multi_line_lint( - '''class Foo { - Bar(int b); - };''', - '') - # comments should be ignored - self.assert_multi_line_lint( - '''class Foo { - // Foo(int f); - };''', - '') - # single argument function following class definition is okay - # (okay, it's not actually valid, but we don't want a false positive) - self.assert_multi_line_lint( - '''class Foo { - Foo(int f, int b); - }; - Foo(int f);''', - '') - # single argument function is okay - self.assert_multi_line_lint( - '''static Foo(int f);''', - '') - # single argument copy constructor is okay. - self.assert_multi_line_lint( - '''class Foo { - Foo(const Foo&); - };''', - '') - self.assert_multi_line_lint( - '''class Foo { - Foo(Foo&); - };''', - '') - - def test_slash_star_comment_on_single_line(self): - self.assert_multi_line_lint( - '''/* static */ Foo(int f);''', - '') - self.assert_multi_line_lint( - '''/*/ static */ Foo(int f);''', - '') - self.assert_multi_line_lint( - '''/*/ static Foo(int f);''', - 'Could not find end of multi-line comment' - ' [readability/multiline_comment] [5]') - self.assert_multi_line_lint( - ''' /*/ static Foo(int f);''', - 'Could not find end of multi-line comment' - ' [readability/multiline_comment] [5]') - self.assert_multi_line_lint( - ''' /**/ static Foo(int f);''', - '') - - # Test suspicious usage of "if" like this: - # if (a == b) { - # DoSomething(); - # } if (a == c) { // Should be "else if". - # DoSomething(); // This gets called twice if a == b && a == c. - # } - def test_suspicious_usage_of_if(self): - self.assert_lint( - ' if (a == b) {', - '') - self.assert_lint( - ' } if (a == b) {', - 'Did you mean "else if"? If not, start a new line for "if".' - ' [readability/braces] [4]') - - # Test suspicious usage of memset. Specifically, a 0 - # as the final argument is almost certainly an error. - def test_suspicious_usage_of_memset(self): - # Normal use is okay. - self.assert_lint( - ' memset(buf, 0, sizeof(buf))', - '') - - # A 0 as the final argument is almost certainly an error. - self.assert_lint( - ' memset(buf, sizeof(buf), 0)', - 'Did you mean "memset(buf, 0, sizeof(buf))"?' - ' [runtime/memset] [4]') - self.assert_lint( - ' memset(buf, xsize * ysize, 0)', - 'Did you mean "memset(buf, 0, xsize * ysize)"?' - ' [runtime/memset] [4]') - - # There is legitimate test code that uses this form. - # This is okay since the second argument is a literal. - self.assert_lint( - " memset(buf, 'y', 0)", - '') - self.assert_lint( - ' memset(buf, 4, 0)', - '') - self.assert_lint( - ' memset(buf, -1, 0)', - '') - self.assert_lint( - ' memset(buf, 0xF1, 0)', - '') - self.assert_lint( - ' memset(buf, 0xcd, 0)', - '') - - def test_check_posix_threading(self): - self.assert_lint('sctime_r()', '') - self.assert_lint('strtok_r()', '') - self.assert_lint(' strtok_r(foo, ba, r)', '') - self.assert_lint('brand()', '') - self.assert_lint('_rand()', '') - self.assert_lint('.rand()', '') - self.assert_lint('>rand()', '') - self.assert_lint('rand()', - 'Consider using rand_r(...) instead of rand(...)' - ' for improved thread safety.' - ' [runtime/threadsafe_fn] [2]') - self.assert_lint('strtok()', - 'Consider using strtok_r(...) ' - 'instead of strtok(...)' - ' for improved thread safety.' - ' [runtime/threadsafe_fn] [2]') - - # Test potential format string bugs like printf(foo). - def test_format_strings(self): - self.assert_lint('printf("foo")', '') - self.assert_lint('printf("foo: %s", foo)', '') - self.assert_lint('DocidForPrintf(docid)', '') # Should not trigger. - self.assert_lint( - 'printf(foo)', - 'Potential format string bug. Do printf("%s", foo) instead.' - ' [runtime/printf] [4]') - self.assert_lint( - 'printf(foo.c_str())', - 'Potential format string bug. ' - 'Do printf("%s", foo.c_str()) instead.' - ' [runtime/printf] [4]') - self.assert_lint( - 'printf(foo->c_str())', - 'Potential format string bug. ' - 'Do printf("%s", foo->c_str()) instead.' - ' [runtime/printf] [4]') - self.assert_lint( - 'StringPrintf(foo)', - 'Potential format string bug. Do StringPrintf("%s", foo) instead.' - '' - ' [runtime/printf] [4]') - - # Variable-length arrays are not permitted. - def test_variable_length_array_detection(self): - errmsg = ('Do not use variable-length arrays. Use an appropriately named ' - "('k' followed by CamelCase) compile-time constant for the size." - ' [runtime/arrays] [1]') - - self.assert_lint('int a[any_old_variable];', errmsg) - self.assert_lint('int doublesize[some_var * 2];', errmsg) - self.assert_lint('int a[afunction()];', errmsg) - self.assert_lint('int a[function(kMaxFooBars)];', errmsg) - self.assert_lint('bool aList[items_->size()];', errmsg) - self.assert_lint('namespace::Type buffer[len+1];', errmsg) - - self.assert_lint('int a[64];', '') - self.assert_lint('int a[0xFF];', '') - self.assert_lint('int first[256], second[256];', '') - self.assert_lint('int arrayName[kCompileTimeConstant];', '') - self.assert_lint('char buf[somenamespace::kBufSize];', '') - self.assert_lint('int arrayName[ALL_CAPS];', '') - self.assert_lint('AClass array1[foo::bar::ALL_CAPS];', '') - self.assert_lint('int a[kMaxStrLen + 1];', '') - self.assert_lint('int a[sizeof(foo)];', '') - self.assert_lint('int a[sizeof(*foo)];', '') - self.assert_lint('int a[sizeof foo];', '') - self.assert_lint('int a[sizeof(struct Foo)];', '') - self.assert_lint('int a[128 - sizeof(const bar)];', '') - self.assert_lint('int a[(sizeof(foo) * 4)];', '') - self.assert_lint('int a[(arraysize(fixed_size_array)/2) << 1];', 'Missing spaces around / [whitespace/operators] [3]') - self.assert_lint('delete a[some_var];', '') - self.assert_lint('return a[some_var];', '') - - # Brace usage - def test_braces(self): - # Braces shouldn't be followed by a ; unless they're defining a struct - # or initializing an array - self.assert_lint('int a[3] = { 1, 2, 3 };', '') - self.assert_lint( - '''const int foo[] = - {1, 2, 3 };''', - '') - # For single line, unmatched '}' with a ';' is ignored (not enough context) - self.assert_multi_line_lint( - '''int a[3] = { 1, - 2, - 3 };''', - '') - self.assert_multi_line_lint( - '''int a[2][3] = { { 1, 2 }, - { 3, 4 } };''', - '') - self.assert_multi_line_lint( - '''int a[2][3] = - { { 1, 2 }, - { 3, 4 } };''', - '') - - # CHECK/EXPECT_TRUE/EXPECT_FALSE replacements - def test_check_check(self): - self.assert_lint('CHECK(x == 42)', - 'Consider using CHECK_EQ instead of CHECK(a == b)' - ' [readability/check] [2]') - self.assert_lint('CHECK(x != 42)', - 'Consider using CHECK_NE instead of CHECK(a != b)' - ' [readability/check] [2]') - self.assert_lint('CHECK(x >= 42)', - 'Consider using CHECK_GE instead of CHECK(a >= b)' - ' [readability/check] [2]') - self.assert_lint('CHECK(x > 42)', - 'Consider using CHECK_GT instead of CHECK(a > b)' - ' [readability/check] [2]') - self.assert_lint('CHECK(x <= 42)', - 'Consider using CHECK_LE instead of CHECK(a <= b)' - ' [readability/check] [2]') - self.assert_lint('CHECK(x < 42)', - 'Consider using CHECK_LT instead of CHECK(a < b)' - ' [readability/check] [2]') - - self.assert_lint('DCHECK(x == 42)', - 'Consider using DCHECK_EQ instead of DCHECK(a == b)' - ' [readability/check] [2]') - self.assert_lint('DCHECK(x != 42)', - 'Consider using DCHECK_NE instead of DCHECK(a != b)' - ' [readability/check] [2]') - self.assert_lint('DCHECK(x >= 42)', - 'Consider using DCHECK_GE instead of DCHECK(a >= b)' - ' [readability/check] [2]') - self.assert_lint('DCHECK(x > 42)', - 'Consider using DCHECK_GT instead of DCHECK(a > b)' - ' [readability/check] [2]') - self.assert_lint('DCHECK(x <= 42)', - 'Consider using DCHECK_LE instead of DCHECK(a <= b)' - ' [readability/check] [2]') - self.assert_lint('DCHECK(x < 42)', - 'Consider using DCHECK_LT instead of DCHECK(a < b)' - ' [readability/check] [2]') - - self.assert_lint( - 'EXPECT_TRUE("42" == x)', - 'Consider using EXPECT_EQ instead of EXPECT_TRUE(a == b)' - ' [readability/check] [2]') - self.assert_lint( - 'EXPECT_TRUE("42" != x)', - 'Consider using EXPECT_NE instead of EXPECT_TRUE(a != b)' - ' [readability/check] [2]') - self.assert_lint( - 'EXPECT_TRUE(+42 >= x)', - 'Consider using EXPECT_GE instead of EXPECT_TRUE(a >= b)' - ' [readability/check] [2]') - self.assert_lint( - 'EXPECT_TRUE_M(-42 > x)', - 'Consider using EXPECT_GT_M instead of EXPECT_TRUE_M(a > b)' - ' [readability/check] [2]') - self.assert_lint( - 'EXPECT_TRUE_M(42U <= x)', - 'Consider using EXPECT_LE_M instead of EXPECT_TRUE_M(a <= b)' - ' [readability/check] [2]') - self.assert_lint( - 'EXPECT_TRUE_M(42L < x)', - 'Consider using EXPECT_LT_M instead of EXPECT_TRUE_M(a < b)' - ' [readability/check] [2]') - - self.assert_lint( - 'EXPECT_FALSE(x == 42)', - 'Consider using EXPECT_NE instead of EXPECT_FALSE(a == b)' - ' [readability/check] [2]') - self.assert_lint( - 'EXPECT_FALSE(x != 42)', - 'Consider using EXPECT_EQ instead of EXPECT_FALSE(a != b)' - ' [readability/check] [2]') - self.assert_lint( - 'EXPECT_FALSE(x >= 42)', - 'Consider using EXPECT_LT instead of EXPECT_FALSE(a >= b)' - ' [readability/check] [2]') - self.assert_lint( - 'ASSERT_FALSE(x > 42)', - 'Consider using ASSERT_LE instead of ASSERT_FALSE(a > b)' - ' [readability/check] [2]') - self.assert_lint( - 'ASSERT_FALSE(x <= 42)', - 'Consider using ASSERT_GT instead of ASSERT_FALSE(a <= b)' - ' [readability/check] [2]') - self.assert_lint( - 'ASSERT_FALSE_M(x < 42)', - 'Consider using ASSERT_GE_M instead of ASSERT_FALSE_M(a < b)' - ' [readability/check] [2]') - - self.assert_lint('CHECK(some_iterator == obj.end())', '') - self.assert_lint('EXPECT_TRUE(some_iterator == obj.end())', '') - self.assert_lint('EXPECT_FALSE(some_iterator == obj.end())', '') - - self.assert_lint('CHECK(CreateTestFile(dir, (1 << 20)));', '') - self.assert_lint('CHECK(CreateTestFile(dir, (1 >> 20)));', '') - - self.assert_lint('CHECK(x<42)', - ['Missing spaces around <' - ' [whitespace/operators] [3]', - 'Consider using CHECK_LT instead of CHECK(a < b)' - ' [readability/check] [2]']) - self.assert_lint('CHECK(x>42)', - 'Consider using CHECK_GT instead of CHECK(a > b)' - ' [readability/check] [2]') - - self.assert_lint( - ' EXPECT_TRUE(42 < x) // Random comment.', - 'Consider using EXPECT_LT instead of EXPECT_TRUE(a < b)' - ' [readability/check] [2]') - self.assert_lint( - 'EXPECT_TRUE( 42 < x )', - ['Extra space after ( in function call' - ' [whitespace/parens] [4]', - 'Consider using EXPECT_LT instead of EXPECT_TRUE(a < b)' - ' [readability/check] [2]']) - self.assert_lint( - 'CHECK("foo" == "foo")', - 'Consider using CHECK_EQ instead of CHECK(a == b)' - ' [readability/check] [2]') - - self.assert_lint('CHECK_EQ("foo", "foo")', '') - - def test_brace_at_begin_of_line(self): - self.assert_lint('{', - 'This { should be at the end of the previous line' - ' [whitespace/braces] [4]') - self.assert_multi_line_lint( - '#endif\n' - '{\n' - '}\n', - '') - self.assert_multi_line_lint( - 'if (condition) {', - '') - self.assert_multi_line_lint( - ' MACRO1(macroArg) {', - '') - self.assert_multi_line_lint( - 'ACCESSOR_GETTER(MessageEventPorts) {', - 'Place brace on its own line for function definitions. [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'int foo() {', - 'Place brace on its own line for function definitions. [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'int foo() const {', - 'Place brace on its own line for function definitions. [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'int foo() const\n' - '{\n' - '}\n', - '') - self.assert_multi_line_lint( - 'if (condition\n' - ' && condition2\n' - ' && condition3) {\n' - '}\n', - '') - - def test_mismatching_spaces_in_parens(self): - self.assert_lint('if (foo ) {', 'Extra space before ) in if' - ' [whitespace/parens] [5]') - self.assert_lint('switch ( foo) {', 'Extra space after ( in switch' - ' [whitespace/parens] [5]') - self.assert_lint('for (foo; ba; bar ) {', 'Extra space before ) in for' - ' [whitespace/parens] [5]') - self.assert_lint('for ((foo); (ba); (bar) ) {', 'Extra space before ) in for' - ' [whitespace/parens] [5]') - self.assert_lint('for (; foo; bar) {', '') - self.assert_lint('for (; (foo); (bar)) {', '') - self.assert_lint('for ( ; foo; bar) {', '') - self.assert_lint('for ( ; (foo); (bar)) {', '') - self.assert_lint('for ( ; foo; bar ) {', 'Extra space before ) in for' - ' [whitespace/parens] [5]') - self.assert_lint('for ( ; (foo); (bar) ) {', 'Extra space before ) in for' - ' [whitespace/parens] [5]') - self.assert_lint('for (foo; bar; ) {', '') - self.assert_lint('for ((foo); (bar); ) {', '') - self.assert_lint('foreach (foo, foos ) {', 'Extra space before ) in foreach' - ' [whitespace/parens] [5]') - self.assert_lint('foreach ( foo, foos) {', 'Extra space after ( in foreach' - ' [whitespace/parens] [5]') - self.assert_lint('while ( foo) {', 'Extra space after ( in while' - ' [whitespace/parens] [5]') - - def test_spacing_for_fncall(self): - self.assert_lint('if (foo) {', '') - self.assert_lint('for (foo;bar;baz) {', '') - self.assert_lint('foreach (foo, foos) {', '') - self.assert_lint('while (foo) {', '') - self.assert_lint('switch (foo) {', '') - self.assert_lint('new (RenderArena()) RenderInline(document())', '') - self.assert_lint('foo( bar)', 'Extra space after ( in function call' - ' [whitespace/parens] [4]') - self.assert_lint('foobar( \\', '') - self.assert_lint('foobar( \\', '') - self.assert_lint('( a + b)', 'Extra space after (' - ' [whitespace/parens] [2]') - self.assert_lint('((a+b))', '') - self.assert_lint('foo (foo)', 'Extra space before ( in function call' - ' [whitespace/parens] [4]') - self.assert_lint('typedef foo (*foo)(foo)', '') - self.assert_lint('typedef foo (*foo12bar_)(foo)', '') - self.assert_lint('typedef foo (Foo::*bar)(foo)', '') - self.assert_lint('foo (Foo::*bar)(', - 'Extra space before ( in function call' - ' [whitespace/parens] [4]') - self.assert_lint('typedef foo (Foo::*bar)(', '') - self.assert_lint('(foo)(bar)', '') - self.assert_lint('Foo (*foo)(bar)', '') - self.assert_lint('Foo (*foo)(Bar bar,', '') - self.assert_lint('char (*p)[sizeof(foo)] = &foo', '') - self.assert_lint('char (&ref)[sizeof(foo)] = &foo', '') - self.assert_lint('const char32 (*table[])[6];', '') - - def test_spacing_before_braces(self): - self.assert_lint('if (foo){', 'Missing space before {' - ' [whitespace/braces] [5]') - self.assert_lint('for{', 'Missing space before {' - ' [whitespace/braces] [5]') - self.assert_lint('for {', '') - self.assert_lint('EXPECT_DEBUG_DEATH({', '') - - def test_spacing_around_else(self): - self.assert_lint('}else {', 'Missing space before else' - ' [whitespace/braces] [5]') - self.assert_lint('} else{', 'Missing space before {' - ' [whitespace/braces] [5]') - self.assert_lint('} else {', '') - self.assert_lint('} else if', '') - - def test_spacing_for_binary_ops(self): - self.assert_lint('if (foo<=bar) {', 'Missing spaces around <=' - ' [whitespace/operators] [3]') - self.assert_lint('if (foo<bar) {', 'Missing spaces around <' - ' [whitespace/operators] [3]') - self.assert_lint('if (foo<bar->baz) {', 'Missing spaces around <' - ' [whitespace/operators] [3]') - self.assert_lint('if (foo<bar->bar) {', 'Missing spaces around <' - ' [whitespace/operators] [3]') - self.assert_lint('typedef hash_map<Foo, Bar', 'Missing spaces around <' - ' [whitespace/operators] [3]') - self.assert_lint('typedef hash_map<FoooooType, BaaaaarType,', '') - self.assert_lint('a<Foo> t+=b;', 'Missing spaces around +=' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo> t-=b;', 'Missing spaces around -=' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t*=b;', 'Missing spaces around *=' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t/=b;', 'Missing spaces around /=' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t|=b;', 'Missing spaces around |=' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t&=b;', 'Missing spaces around &=' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t<<=b;', 'Missing spaces around <<=' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t>>=b;', 'Missing spaces around >>=' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t>>=&b|c;', 'Missing spaces around >>=' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t<<=*b/c;', 'Missing spaces around <<=' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo> t -= b;', '') - self.assert_lint('a<Foo> t += b;', '') - self.assert_lint('a<Foo*> t *= b;', '') - self.assert_lint('a<Foo*> t /= b;', '') - self.assert_lint('a<Foo*> t |= b;', '') - self.assert_lint('a<Foo*> t &= b;', '') - self.assert_lint('a<Foo*> t <<= b;', '') - self.assert_lint('a<Foo*> t >>= b;', '') - self.assert_lint('a<Foo*> t >>= &b|c;', 'Missing spaces around |' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t <<= *b/c;', 'Missing spaces around /' - ' [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t <<= b/c; //Test', [ - 'Should have a space between // and comment ' - '[whitespace/comments] [4]', 'Missing' - ' spaces around / [whitespace/operators] [3]']) - self.assert_lint('a<Foo*> t <<= b||c; //Test', ['One space before end' - ' of line comments [whitespace/comments] [5]', - 'Should have a space between // and comment ' - '[whitespace/comments] [4]', - 'Missing spaces around || [whitespace/operators] [3]']) - self.assert_lint('a<Foo*> t <<= b&&c; // Test', 'Missing spaces around' - ' && [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t <<= b&&&c; // Test', 'Missing spaces around' - ' && [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t <<= b&&*c; // Test', 'Missing spaces around' - ' && [whitespace/operators] [3]') - self.assert_lint('a<Foo*> t <<= b && *c; // Test', '') - self.assert_lint('a<Foo*> t <<= b && &c; // Test', '') - self.assert_lint('a<Foo*> t <<= b || &c; /*Test', 'Complex multi-line ' - '/*...*/-style comment found. Lint may give bogus ' - 'warnings. Consider replacing these with //-style' - ' comments, with #if 0...#endif, or with more clearly' - ' structured multi-line comments. [readability/multiline_comment] [5]') - self.assert_lint('a<Foo&> t <<= &b | &c;', '') - self.assert_lint('a<Foo*> t <<= &b & &c; // Test', '') - self.assert_lint('a<Foo*> t <<= *b / &c; // Test', '') - self.assert_lint('if (a=b == 1)', 'Missing spaces around = [whitespace/operators] [4]') - self.assert_lint('a = 1<<20', 'Missing spaces around << [whitespace/operators] [3]') - self.assert_lint('if (a = b == 1)', '') - self.assert_lint('a = 1 << 20', '') - self.assert_multi_line_lint('#include "config.h"\n#include <sys/io.h>\n', - '') - self.assert_multi_line_lint('#include "config.h"\n#import <foo/bar.h>\n', - '') - - def test_operator_methods(self): - self.assert_lint('String operator+(const String&, const String&);', '') - self.assert_lint('bool operator==(const String&, const String&);', '') - self.assert_lint('String& operator-=(const String&, const String&);', '') - self.assert_lint('String& operator+=(const String&, const String&);', '') - self.assert_lint('String& operator*=(const String&, const String&);', '') - self.assert_lint('String& operator%=(const String&, const String&);', '') - self.assert_lint('String& operator&=(const String&, const String&);', '') - self.assert_lint('String& operator<<=(const String&, const String&);', '') - self.assert_lint('String& operator>>=(const String&, const String&);', '') - self.assert_lint('String& operator|=(const String&, const String&);', '') - self.assert_lint('String& operator^=(const String&, const String&);', '') - - def test_spacing_before_last_semicolon(self): - self.assert_lint('call_function() ;', - 'Extra space before last semicolon. If this should be an ' - 'empty statement, use { } instead.' - ' [whitespace/semicolon] [5]') - self.assert_lint('while (true) ;', - 'Extra space before last semicolon. If this should be an ' - 'empty statement, use { } instead.' - ' [whitespace/semicolon] [5]') - self.assert_lint('default:;', - 'Semicolon defining empty statement. Use { } instead.' - ' [whitespace/semicolon] [5]') - self.assert_lint(' ;', - 'Line contains only semicolon. If this should be an empty ' - 'statement, use { } instead.' - ' [whitespace/semicolon] [5]') - self.assert_lint('for (int i = 0; ;', '') - - # Static or global STL strings. - def test_static_or_global_stlstrings(self): - self.assert_lint('string foo;', - 'For a static/global string constant, use a C style ' - 'string instead: "char foo[]".' - ' [runtime/string] [4]') - self.assert_lint('string kFoo = "hello"; // English', - 'For a static/global string constant, use a C style ' - 'string instead: "char kFoo[]".' - ' [runtime/string] [4]') - self.assert_lint('static string foo;', - 'For a static/global string constant, use a C style ' - 'string instead: "static char foo[]".' - ' [runtime/string] [4]') - self.assert_lint('static const string foo;', - 'For a static/global string constant, use a C style ' - 'string instead: "static const char foo[]".' - ' [runtime/string] [4]') - self.assert_lint('string Foo::bar;', - 'For a static/global string constant, use a C style ' - 'string instead: "char Foo::bar[]".' - ' [runtime/string] [4]') - # Rare case. - self.assert_lint('string foo("foobar");', - 'For a static/global string constant, use a C style ' - 'string instead: "char foo[]".' - ' [runtime/string] [4]') - # Should not catch local or member variables. - self.assert_lint(' string foo', '') - # Should not catch functions. - self.assert_lint('string EmptyString() { return ""; }', '') - self.assert_lint('string EmptyString () { return ""; }', '') - self.assert_lint('string VeryLongNameFunctionSometimesEndsWith(\n' - ' VeryLongNameType very_long_name_variable) {}', '') - self.assert_lint('template<>\n' - 'string FunctionTemplateSpecialization<SomeType>(\n' - ' int x) { return ""; }', '') - self.assert_lint('template<>\n' - 'string FunctionTemplateSpecialization<vector<A::B>* >(\n' - ' int x) { return ""; }', '') - - # should not catch methods of template classes. - self.assert_lint('string Class<Type>::Method() const\n' - '{\n' - ' return "";\n' - '}\n', '') - self.assert_lint('string Class<Type>::Method(\n' - ' int arg) const\n' - '{\n' - ' return "";\n' - '}\n', '') - - def test_no_spaces_in_function_calls(self): - self.assert_lint('TellStory(1, 3);', - '') - self.assert_lint('TellStory(1, 3 );', - 'Extra space before )' - ' [whitespace/parens] [2]') - self.assert_lint('TellStory(1 /* wolf */, 3 /* pigs */);', - '') - self.assert_multi_line_lint('#endif\n );', - '') - - def test_two_spaces_between_code_and_comments(self): - self.assert_lint('} // namespace foo', - '') - self.assert_lint('}// namespace foo', - 'One space before end of line comments' - ' [whitespace/comments] [5]') - self.assert_lint('printf("foo"); // Outside quotes.', - '') - self.assert_lint('int i = 0; // Having one space is fine.','') - self.assert_lint('int i = 0; // Having two spaces is bad.', - 'One space before end of line comments' - ' [whitespace/comments] [5]') - self.assert_lint('int i = 0; // Having three spaces is bad.', - 'One space before end of line comments' - ' [whitespace/comments] [5]') - self.assert_lint('// Top level comment', '') - self.assert_lint(' // Line starts with four spaces.', '') - self.assert_lint('foo();\n' - '{ // A scope is opening.', '') - self.assert_lint(' foo();\n' - ' { // An indented scope is opening.', '') - self.assert_lint('if (foo) { // not a pure scope', - '') - self.assert_lint('printf("// In quotes.")', '') - self.assert_lint('printf("\\"%s // In quotes.")', '') - self.assert_lint('printf("%s", "// In quotes.")', '') - - def test_space_after_comment_marker(self): - self.assert_lint('//', '') - self.assert_lint('//x', 'Should have a space between // and comment' - ' [whitespace/comments] [4]') - self.assert_lint('// x', '') - self.assert_lint('//----', '') - self.assert_lint('//====', '') - self.assert_lint('//////', '') - self.assert_lint('////// x', '') - self.assert_lint('/// x', '') - self.assert_lint('////x', 'Should have a space between // and comment' - ' [whitespace/comments] [4]') - - def test_newline_at_eof(self): - def do_test(self, data, is_missing_eof): - error_collector = ErrorCollector(self.assert_) - self.process_file_data('foo.cpp', 'cpp', data.split('\n'), - error_collector) - # The warning appears only once. - self.assertEquals( - int(is_missing_eof), - error_collector.results().count( - 'Could not find a newline character at the end of the file.' - ' [whitespace/ending_newline] [5]')) - - do_test(self, '// Newline\n// at EOF\n', False) - do_test(self, '// No newline\n// at EOF', True) - - def test_invalid_utf8(self): - def do_test(self, raw_bytes, has_invalid_utf8): - error_collector = ErrorCollector(self.assert_) - self.process_file_data('foo.cpp', 'cpp', - unicode(raw_bytes, 'utf8', 'replace').split('\n'), - error_collector) - # The warning appears only once. - self.assertEquals( - int(has_invalid_utf8), - error_collector.results().count( - 'Line contains invalid UTF-8' - ' (or Unicode replacement character).' - ' [readability/utf8] [5]')) - - do_test(self, 'Hello world\n', False) - do_test(self, '\xe9\x8e\xbd\n', False) - do_test(self, '\xe9x\x8e\xbd\n', True) - # This is the encoding of the replacement character itself (which - # you can see by evaluating codecs.getencoder('utf8')(u'\ufffd')). - do_test(self, '\xef\xbf\xbd\n', True) - - def test_is_blank_line(self): - self.assert_(cpp_style.is_blank_line('')) - self.assert_(cpp_style.is_blank_line(' ')) - self.assert_(cpp_style.is_blank_line(' \t\r\n')) - self.assert_(not cpp_style.is_blank_line('int a;')) - self.assert_(not cpp_style.is_blank_line('{')) - - def test_blank_lines_check(self): - self.assert_blank_lines_check(['{\n', '\n', '\n', '}\n'], 1, 1) - self.assert_blank_lines_check([' if (foo) {\n', '\n', ' }\n'], 1, 1) - self.assert_blank_lines_check( - ['\n', '// {\n', '\n', '\n', '// Comment\n', '{\n', '}\n'], 0, 0) - self.assert_blank_lines_check(['\n', 'run("{");\n', '\n'], 0, 0) - self.assert_blank_lines_check(['\n', ' if (foo) { return 0; }\n', '\n'], 0, 0) - - def test_allow_blank_line_before_closing_namespace(self): - error_collector = ErrorCollector(self.assert_) - self.process_file_data('foo.cpp', 'cpp', - ['namespace {', '', '} // namespace'], - error_collector) - self.assertEquals(0, error_collector.results().count( - 'Blank line at the end of a code block. Is this needed?' - ' [whitespace/blank_line] [3]')) - - def test_allow_blank_line_before_if_else_chain(self): - error_collector = ErrorCollector(self.assert_) - self.process_file_data('foo.cpp', 'cpp', - ['if (hoge) {', - '', # No warning - '} else if (piyo) {', - '', # No warning - '} else if (piyopiyo) {', - ' hoge = true;', # No warning - '} else {', - '', # Warning on this line - '}'], - error_collector) - self.assertEquals(1, error_collector.results().count( - 'Blank line at the end of a code block. Is this needed?' - ' [whitespace/blank_line] [3]')) - - def test_else_on_same_line_as_closing_braces(self): - error_collector = ErrorCollector(self.assert_) - self.process_file_data('foo.cpp', 'cpp', - ['if (hoge) {', - '', - '}', - ' else {' # Warning on this line - '', - '}'], - error_collector) - self.assertEquals(1, error_collector.results().count( - 'An else should appear on the same line as the preceding }' - ' [whitespace/newline] [4]')) - - def test_else_clause_not_on_same_line_as_else(self): - self.assert_lint(' else DoSomethingElse();', - 'Else clause should never be on same line as else ' - '(use 2 lines) [whitespace/newline] [4]') - self.assert_lint(' else ifDoSomethingElse();', - 'Else clause should never be on same line as else ' - '(use 2 lines) [whitespace/newline] [4]') - self.assert_lint(' else if (blah) {', '') - self.assert_lint(' variable_ends_in_else = true;', '') - - def test_comma(self): - self.assert_lint('a = f(1,2);', - 'Missing space after , [whitespace/comma] [3]') - self.assert_lint('int tmp=a,a=b,b=tmp;', - ['Missing spaces around = [whitespace/operators] [4]', - 'Missing space after , [whitespace/comma] [3]']) - self.assert_lint('f(a, /* name */ b);', '') - self.assert_lint('f(a, /* name */b);', '') - - def test_declaration(self): - self.assert_lint('int a;', '') - self.assert_lint('int a;', 'Extra space between int and a [whitespace/declaration] [3]') - self.assert_lint('int* a;', 'Extra space between int* and a [whitespace/declaration] [3]') - self.assert_lint('else if { }', '') - self.assert_lint('else if { }', 'Extra space between else and if [whitespace/declaration] [3]') - - def test_pointer_reference_marker_location(self): - self.assert_lint('int* b;', '', 'foo.cpp') - self.assert_lint('int *b;', - 'Declaration has space between type name and * in int *b [whitespace/declaration] [3]', - 'foo.cpp') - self.assert_lint('return *b;', '', 'foo.cpp') - self.assert_lint('delete *b;', '', 'foo.cpp') - self.assert_lint('int *b;', '', 'foo.c') - self.assert_lint('int* b;', - 'Declaration has space between * and variable name in int* b [whitespace/declaration] [3]', - 'foo.c') - self.assert_lint('int& b;', '', 'foo.cpp') - self.assert_lint('int &b;', - 'Declaration has space between type name and & in int &b [whitespace/declaration] [3]', - 'foo.cpp') - self.assert_lint('return &b;', '', 'foo.cpp') - - def test_indent(self): - self.assert_lint('static int noindent;', '') - self.assert_lint(' int fourSpaceIndent;', '') - self.assert_lint(' int oneSpaceIndent;', - 'Weird number of spaces at line-start. ' - 'Are you using a 4-space indent? [whitespace/indent] [3]') - self.assert_lint(' int threeSpaceIndent;', - 'Weird number of spaces at line-start. ' - 'Are you using a 4-space indent? [whitespace/indent] [3]') - self.assert_lint(' char* oneSpaceIndent = "public:";', - 'Weird number of spaces at line-start. ' - 'Are you using a 4-space indent? [whitespace/indent] [3]') - self.assert_lint(' public:', '') - self.assert_lint(' public:', '') - self.assert_lint(' public:', '') - - def test_label(self): - self.assert_lint('public:', - 'Labels should always be indented at least one space. ' - 'If this is a member-initializer list in a constructor, ' - 'the colon should be on the line after the definition ' - 'header. [whitespace/labels] [4]') - self.assert_lint(' public:', '') - self.assert_lint(' public:', '') - self.assert_lint(' public:', '') - self.assert_lint(' public:', '') - self.assert_lint(' public:', '') - - def test_not_alabel(self): - self.assert_lint('MyVeryLongNamespace::MyVeryLongClassName::', '') - - def test_tab(self): - self.assert_lint('\tint a;', - 'Tab found; better to use spaces [whitespace/tab] [1]') - self.assert_lint('int a = 5;\t// set a to 5', - 'Tab found; better to use spaces [whitespace/tab] [1]') - - def test_unnamed_namespaces_in_headers(self): - self.assert_language_rules_check( - 'foo.h', 'namespace {', - 'Do not use unnamed namespaces in header files. See' - ' http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' - ' for more information. [build/namespaces] [4]') - # namespace registration macros are OK. - self.assert_language_rules_check('foo.h', 'namespace { \\', '') - # named namespaces are OK. - self.assert_language_rules_check('foo.h', 'namespace foo {', '') - self.assert_language_rules_check('foo.h', 'namespace foonamespace {', '') - self.assert_language_rules_check('foo.cpp', 'namespace {', '') - self.assert_language_rules_check('foo.cpp', 'namespace foo {', '') - - def test_build_class(self): - # Test that the linter can parse to the end of class definitions, - # and that it will report when it can't. - # Use multi-line linter because it performs the ClassState check. - self.assert_multi_line_lint( - 'class Foo {', - 'Failed to find complete declaration of class Foo' - ' [build/class] [5]') - # Don't warn on forward declarations of various types. - self.assert_multi_line_lint( - 'class Foo;', - '') - self.assert_multi_line_lint( - '''struct Foo* - foo = NewFoo();''', - '') - # Here is an example where the linter gets confused, even though - # the code doesn't violate the style guide. - self.assert_multi_line_lint( - '''class Foo - #ifdef DERIVE_FROM_GOO - : public Goo { - #else - : public Hoo { - #endif - };''', - 'Failed to find complete declaration of class Foo' - ' [build/class] [5]') - - def test_build_end_comment(self): - # The crosstool compiler we currently use will fail to compile the - # code in this test, so we might consider removing the lint check. - self.assert_lint('#endif Not a comment', - 'Uncommented text after #endif is non-standard.' - ' Use a comment.' - ' [build/endif_comment] [5]') - - def test_build_forward_decl(self): - # The crosstool compiler we currently use will fail to compile the - # code in this test, so we might consider removing the lint check. - self.assert_lint('class Foo::Goo;', - 'Inner-style forward declarations are invalid.' - ' Remove this line.' - ' [build/forward_decl] [5]') - - def test_build_header_guard(self): - file_path = 'mydir/Foo.h' - - # We can't rely on our internal stuff to get a sane path on the open source - # side of things, so just parse out the suggested header guard. This - # doesn't allow us to test the suggested header guard, but it does let us - # test all the other header tests. - error_collector = ErrorCollector(self.assert_) - self.process_file_data(file_path, 'h', [], error_collector) - expected_guard = '' - matcher = re.compile( - 'No \#ifndef header guard found\, suggested CPP variable is\: ([A-Za-z_0-9]+) ') - for error in error_collector.result_list(): - matches = matcher.match(error) - if matches: - expected_guard = matches.group(1) - break - - # Make sure we extracted something for our header guard. - self.assertNotEqual(expected_guard, '') - - # Wrong guard - error_collector = ErrorCollector(self.assert_) - self.process_file_data(file_path, 'h', - ['#ifndef FOO_H', '#define FOO_H'], error_collector) - self.assertEquals( - 1, - error_collector.result_list().count( - '#ifndef header guard has wrong style, please use: %s' - ' [build/header_guard] [5]' % expected_guard), - error_collector.result_list()) - - # No define - error_collector = ErrorCollector(self.assert_) - self.process_file_data(file_path, 'h', - ['#ifndef %s' % expected_guard], error_collector) - self.assertEquals( - 1, - error_collector.result_list().count( - 'No #ifndef header guard found, suggested CPP variable is: %s' - ' [build/header_guard] [5]' % expected_guard), - error_collector.result_list()) - - # Mismatched define - error_collector = ErrorCollector(self.assert_) - self.process_file_data(file_path, 'h', - ['#ifndef %s' % expected_guard, - '#define FOO_H'], - error_collector) - self.assertEquals( - 1, - error_collector.result_list().count( - 'No #ifndef header guard found, suggested CPP variable is: %s' - ' [build/header_guard] [5]' % expected_guard), - error_collector.result_list()) - - # No header guard errors - error_collector = ErrorCollector(self.assert_) - self.process_file_data(file_path, 'h', - ['#ifndef %s' % expected_guard, - '#define %s' % expected_guard, - '#endif // %s' % expected_guard], - error_collector) - for line in error_collector.result_list(): - if line.find('build/header_guard') != -1: - self.fail('Unexpected error: %s' % line) - - # Completely incorrect header guard - error_collector = ErrorCollector(self.assert_) - self.process_file_data(file_path, 'h', - ['#ifndef FOO', - '#define FOO', - '#endif // FOO'], - error_collector) - self.assertEquals( - 1, - error_collector.result_list().count( - '#ifndef header guard has wrong style, please use: %s' - ' [build/header_guard] [5]' % expected_guard), - error_collector.result_list()) - - # Special case for flymake - error_collector = ErrorCollector(self.assert_) - self.process_file_data('mydir/Foo_flymake.h', 'h', - ['#ifndef %s' % expected_guard, - '#define %s' % expected_guard, - '#endif // %s' % expected_guard], - error_collector) - for line in error_collector.result_list(): - if line.find('build/header_guard') != -1: - self.fail('Unexpected error: %s' % line) - - error_collector = ErrorCollector(self.assert_) - self.process_file_data('mydir/Foo_flymake.h', 'h', [], error_collector) - self.assertEquals( - 1, - error_collector.result_list().count( - 'No #ifndef header guard found, suggested CPP variable is: %s' - ' [build/header_guard] [5]' % expected_guard), - error_collector.result_list()) - - def test_build_printf_format(self): - self.assert_lint( - r'printf("\%%d", value);', - '%, [, (, and { are undefined character escapes. Unescape them.' - ' [build/printf_format] [3]') - - self.assert_lint( - r'snprintf(buffer, sizeof(buffer), "\[%d", value);', - '%, [, (, and { are undefined character escapes. Unescape them.' - ' [build/printf_format] [3]') - - self.assert_lint( - r'fprintf(file, "\(%d", value);', - '%, [, (, and { are undefined character escapes. Unescape them.' - ' [build/printf_format] [3]') - - self.assert_lint( - r'vsnprintf(buffer, sizeof(buffer), "\\\{%d", ap);', - '%, [, (, and { are undefined character escapes. Unescape them.' - ' [build/printf_format] [3]') - - # Don't warn if double-slash precedes the symbol - self.assert_lint(r'printf("\\%%%d", value);', - '') - - def test_runtime_printf_format(self): - self.assert_lint( - r'fprintf(file, "%q", value);', - '%q in format strings is deprecated. Use %ll instead.' - ' [runtime/printf_format] [3]') - - self.assert_lint( - r'aprintf(file, "The number is %12q", value);', - '%q in format strings is deprecated. Use %ll instead.' - ' [runtime/printf_format] [3]') - - self.assert_lint( - r'printf(file, "The number is" "%-12q", value);', - '%q in format strings is deprecated. Use %ll instead.' - ' [runtime/printf_format] [3]') - - self.assert_lint( - r'printf(file, "The number is" "%+12q", value);', - '%q in format strings is deprecated. Use %ll instead.' - ' [runtime/printf_format] [3]') - - self.assert_lint( - r'printf(file, "The number is" "% 12q", value);', - '%q in format strings is deprecated. Use %ll instead.' - ' [runtime/printf_format] [3]') - - self.assert_lint( - r'snprintf(file, "Never mix %d and %1$d parmaeters!", value);', - '%N$ formats are unconventional. Try rewriting to avoid them.' - ' [runtime/printf_format] [2]') - - def assert_lintLogCodeOnError(self, code, expected_message): - # Special assert_lint which logs the input code on error. - result = self.perform_single_line_lint(code, 'foo.cpp') - if result != expected_message: - self.fail('For code: "%s"\nGot: "%s"\nExpected: "%s"' - % (code, result, expected_message)) - - def test_build_storage_class(self): - qualifiers = [None, 'const', 'volatile'] - signs = [None, 'signed', 'unsigned'] - types = ['void', 'char', 'int', 'float', 'double', - 'schar', 'int8', 'uint8', 'int16', 'uint16', - 'int32', 'uint32', 'int64', 'uint64'] - storage_classes = ['auto', 'extern', 'register', 'static', 'typedef'] - - build_storage_class_error_message = ( - 'Storage class (static, extern, typedef, etc) should be first.' - ' [build/storage_class] [5]') - - # Some explicit cases. Legal in C++, deprecated in C99. - self.assert_lint('const int static foo = 5;', - build_storage_class_error_message) - - self.assert_lint('char static foo;', - build_storage_class_error_message) - - self.assert_lint('double const static foo = 2.0;', - build_storage_class_error_message) - - self.assert_lint('uint64 typedef unsignedLongLong;', - build_storage_class_error_message) - - self.assert_lint('int register foo = 0;', - build_storage_class_error_message) - - # Since there are a very large number of possibilities, randomly - # construct declarations. - # Make sure that the declaration is logged if there's an error. - # Seed generator with an integer for absolute reproducibility. - random.seed(25) - for unused_i in range(10): - # Build up random list of non-storage-class declaration specs. - other_decl_specs = [random.choice(qualifiers), random.choice(signs), - random.choice(types)] - # remove None - other_decl_specs = filter(lambda x: x is not None, other_decl_specs) - - # shuffle - random.shuffle(other_decl_specs) - - # insert storage class after the first - storage_class = random.choice(storage_classes) - insertion_point = random.randint(1, len(other_decl_specs)) - decl_specs = (other_decl_specs[0:insertion_point] - + [storage_class] - + other_decl_specs[insertion_point:]) - - self.assert_lintLogCodeOnError( - ' '.join(decl_specs) + ';', - build_storage_class_error_message) - - # but no error if storage class is first - self.assert_lintLogCodeOnError( - storage_class + ' ' + ' '.join(other_decl_specs), - '') - - def test_legal_copyright(self): - legal_copyright_message = ( - 'No copyright message found. ' - 'You should have a line: "Copyright [year] <Copyright Owner>"' - ' [legal/copyright] [5]') - - copyright_line = '// Copyright 2008 Google Inc. All Rights Reserved.' - - file_path = 'mydir/googleclient/foo.cpp' - - # There should be a copyright message in the first 10 lines - error_collector = ErrorCollector(self.assert_) - self.process_file_data(file_path, 'cpp', [], error_collector) - self.assertEquals( - 1, - error_collector.result_list().count(legal_copyright_message)) - - error_collector = ErrorCollector(self.assert_) - self.process_file_data( - file_path, 'cpp', - ['' for unused_i in range(10)] + [copyright_line], - error_collector) - self.assertEquals( - 1, - error_collector.result_list().count(legal_copyright_message)) - - # Test that warning isn't issued if Copyright line appears early enough. - error_collector = ErrorCollector(self.assert_) - self.process_file_data(file_path, 'cpp', [copyright_line], error_collector) - for message in error_collector.result_list(): - if message.find('legal/copyright') != -1: - self.fail('Unexpected error: %s' % message) - - error_collector = ErrorCollector(self.assert_) - self.process_file_data( - file_path, 'cpp', - ['' for unused_i in range(9)] + [copyright_line], - error_collector) - for message in error_collector.result_list(): - if message.find('legal/copyright') != -1: - self.fail('Unexpected error: %s' % message) - - def test_invalid_increment(self): - self.assert_lint('*count++;', - 'Changing pointer instead of value (or unused value of ' - 'operator*). [runtime/invalid_increment] [5]') - - -class CleansedLinesTest(unittest.TestCase): - def test_init(self): - lines = ['Line 1', - 'Line 2', - 'Line 3 // Comment test', - 'Line 4 "foo"'] - - clean_lines = cpp_style.CleansedLines(lines) - self.assertEquals(lines, clean_lines.raw_lines) - self.assertEquals(4, clean_lines.num_lines()) - - self.assertEquals(['Line 1', - 'Line 2', - 'Line 3 ', - 'Line 4 "foo"'], - clean_lines.lines) - - self.assertEquals(['Line 1', - 'Line 2', - 'Line 3 ', - 'Line 4 ""'], - clean_lines.elided) - - def test_init_empty(self): - clean_lines = cpp_style.CleansedLines([]) - self.assertEquals([], clean_lines.raw_lines) - self.assertEquals(0, clean_lines.num_lines()) - - def test_collapse_strings(self): - collapse = cpp_style.CleansedLines.collapse_strings - self.assertEquals('""', collapse('""')) # "" (empty) - self.assertEquals('"""', collapse('"""')) # """ (bad) - self.assertEquals('""', collapse('"xyz"')) # "xyz" (string) - self.assertEquals('""', collapse('"\\\""')) # "\"" (string) - self.assertEquals('""', collapse('"\'"')) # "'" (string) - self.assertEquals('"\"', collapse('"\"')) # "\" (bad) - self.assertEquals('""', collapse('"\\\\"')) # "\\" (string) - self.assertEquals('"', collapse('"\\\\\\"')) # "\\\" (bad) - self.assertEquals('""', collapse('"\\\\\\\\"')) # "\\\\" (string) - - self.assertEquals('\'\'', collapse('\'\'')) # '' (empty) - self.assertEquals('\'\'', collapse('\'a\'')) # 'a' (char) - self.assertEquals('\'\'', collapse('\'\\\'\'')) # '\'' (char) - self.assertEquals('\'', collapse('\'\\\'')) # '\' (bad) - self.assertEquals('', collapse('\\012')) # '\012' (char) - self.assertEquals('', collapse('\\xfF0')) # '\xfF0' (char) - self.assertEquals('', collapse('\\n')) # '\n' (char) - self.assertEquals('\#', collapse('\\#')) # '\#' (bad) - - self.assertEquals('StringReplace(body, "", "");', - collapse('StringReplace(body, "\\\\", "\\\\\\\\");')) - self.assertEquals('\'\' ""', - collapse('\'"\' "foo"')) - - -class OrderOfIncludesTest(CppStyleTestBase): - def setUp(self): - self.include_state = cpp_style._IncludeState() - - # Cheat os.path.abspath called in FileInfo class. - self.os_path_abspath_orig = os.path.abspath - os.path.abspath = lambda value: value - - def tearDown(self): - os.path.abspath = self.os_path_abspath_orig - - def test_try_drop_common_suffixes(self): - self.assertEqual('foo/foo', cpp_style._drop_common_suffixes('foo/foo-inl.h')) - self.assertEqual('foo/bar/foo', - cpp_style._drop_common_suffixes('foo/bar/foo_inl.h')) - self.assertEqual('foo/foo', cpp_style._drop_common_suffixes('foo/foo.cpp')) - self.assertEqual('foo/foo_unusualinternal', - cpp_style._drop_common_suffixes('foo/foo_unusualinternal.h')) - self.assertEqual('', - cpp_style._drop_common_suffixes('_test.cpp')) - self.assertEqual('test', - cpp_style._drop_common_suffixes('test.cpp')) - - -class OrderOfIncludesTest(CppStyleTestBase): - def setUp(self): - self.include_state = cpp_style._IncludeState() - - # Cheat os.path.abspath called in FileInfo class. - self.os_path_abspath_orig = os.path.abspath - os.path.abspath = lambda value: value - - def tearDown(self): - os.path.abspath = self.os_path_abspath_orig - - def test_check_next_include_order__no_config(self): - self.assertEqual('Header file should not contain WebCore config.h.', - self.include_state.check_next_include_order(cpp_style._CONFIG_HEADER, True)) - - def test_check_next_include_order__no_self(self): - self.assertEqual('Header file should not contain itself.', - self.include_state.check_next_include_order(cpp_style._PRIMARY_HEADER, True)) - # Test actual code to make sure that header types are correctly assigned. - self.assert_language_rules_check('Foo.h', - '#include "Foo.h"\n', - 'Header file should not contain itself. Should be: alphabetically sorted.' - ' [build/include_order] [4]') - self.assert_language_rules_check('FooBar.h', - '#include "Foo.h"\n', - '') - - def test_check_next_include_order__likely_then_config(self): - self.assertEqual('Found header this file implements before WebCore config.h.', - self.include_state.check_next_include_order(cpp_style._PRIMARY_HEADER, False)) - self.assertEqual('Found WebCore config.h after a header this file implements.', - self.include_state.check_next_include_order(cpp_style._CONFIG_HEADER, False)) - - def test_check_next_include_order__other_then_config(self): - self.assertEqual('Found other header before WebCore config.h.', - self.include_state.check_next_include_order(cpp_style._OTHER_HEADER, False)) - self.assertEqual('Found WebCore config.h after other header.', - self.include_state.check_next_include_order(cpp_style._CONFIG_HEADER, False)) - - def test_check_next_include_order__config_then_other_then_likely(self): - self.assertEqual('', self.include_state.check_next_include_order(cpp_style._CONFIG_HEADER, False)) - self.assertEqual('Found other header before a header this file implements.', - self.include_state.check_next_include_order(cpp_style._OTHER_HEADER, False)) - self.assertEqual('Found header this file implements after other header.', - self.include_state.check_next_include_order(cpp_style._PRIMARY_HEADER, False)) - - def test_check_alphabetical_include_order(self): - self.assert_language_rules_check('foo.h', - '#include "a.h"\n' - '#include "c.h"\n' - '#include "b.h"\n', - 'Alphabetical sorting problem. [build/include_order] [4]') - - self.assert_language_rules_check('foo.h', - '#include "a.h"\n' - '#include "b.h"\n' - '#include "c.h"\n', - '') - - self.assert_language_rules_check('foo.h', - '#include <assert.h>\n' - '#include "bar.h"\n', - 'Alphabetical sorting problem. [build/include_order] [4]') - - self.assert_language_rules_check('foo.h', - '#include "bar.h"\n' - '#include <assert.h>\n', - '') - - def test_check_line_break_after_own_header(self): - self.assert_language_rules_check('foo.cpp', - '#include "config.h"\n' - '#include "foo.h"\n' - '#include "bar.h"\n', - 'You should add a blank line after implementation file\'s own header. [build/include_order] [4]') - - self.assert_language_rules_check('foo.cpp', - '#include "config.h"\n' - '#include "foo.h"\n' - '\n' - '#include "bar.h"\n', - '') - - def test_check_preprocessor_in_include_section(self): - self.assert_language_rules_check('foo.cpp', - '#include "config.h"\n' - '#include "foo.h"\n' - '\n' - '#ifdef BAZ\n' - '#include "baz.h"\n' - '#else\n' - '#include "foobar.h"\n' - '#endif"\n' - '#include "bar.h"\n', # No flag because previous is in preprocessor section - '') - - self.assert_language_rules_check('foo.cpp', - '#include "config.h"\n' - '#include "foo.h"\n' - '\n' - '#ifdef BAZ\n' - '#include "baz.h"\n' - '#endif"\n' - '#include "bar.h"\n' - '#include "a.h"\n', # Should still flag this. - 'Alphabetical sorting problem. [build/include_order] [4]') - - self.assert_language_rules_check('foo.cpp', - '#include "config.h"\n' - '#include "foo.h"\n' - '\n' - '#ifdef BAZ\n' - '#include "baz.h"\n' - '#include "bar.h"\n' #Should still flag this - '#endif"\n', - 'Alphabetical sorting problem. [build/include_order] [4]') - - self.assert_language_rules_check('foo.cpp', - '#include "config.h"\n' - '#include "foo.h"\n' - '\n' - '#ifdef BAZ\n' - '#include "baz.h"\n' - '#endif"\n' - '#ifdef FOOBAR\n' - '#include "foobar.h"\n' - '#endif"\n' - '#include "bar.h"\n' - '#include "a.h"\n', # Should still flag this. - 'Alphabetical sorting problem. [build/include_order] [4]') - - # Check that after an already included error, the sorting rules still work. - self.assert_language_rules_check('foo.cpp', - '#include "config.h"\n' - '#include "foo.h"\n' - '\n' - '#include "foo.h"\n' - '#include "g.h"\n', - '"foo.h" already included at foo.cpp:1 [build/include] [4]') - - def test_check_wtf_includes(self): - self.assert_language_rules_check('foo.cpp', - '#include "config.h"\n' - '#include "foo.h"\n' - '\n' - '#include <wtf/Assertions.h>\n', - '') - self.assert_language_rules_check('foo.cpp', - '#include "config.h"\n' - '#include "foo.h"\n' - '\n' - '#include "wtf/Assertions.h"\n', - 'wtf includes should be <wtf/file.h> instead of "wtf/file.h".' - ' [build/include] [4]') - - def test_classify_include(self): - classify_include = cpp_style._classify_include - include_state = cpp_style._IncludeState() - self.assertEqual(cpp_style._CONFIG_HEADER, - classify_include('foo/foo.cpp', - 'config.h', - False, include_state)) - self.assertEqual(cpp_style._PRIMARY_HEADER, - classify_include('foo/internal/foo.cpp', - 'foo/public/foo.h', - False, include_state)) - self.assertEqual(cpp_style._PRIMARY_HEADER, - classify_include('foo/internal/foo.cpp', - 'foo/other/public/foo.h', - False, include_state)) - self.assertEqual(cpp_style._OTHER_HEADER, - classify_include('foo/internal/foo.cpp', - 'foo/other/public/foop.h', - False, include_state)) - self.assertEqual(cpp_style._OTHER_HEADER, - classify_include('foo/foo.cpp', - 'string', - True, include_state)) - self.assertEqual(cpp_style._PRIMARY_HEADER, - classify_include('fooCustom.cpp', - 'foo.h', - False, include_state)) - self.assertEqual(cpp_style._PRIMARY_HEADER, - classify_include('PrefixFooCustom.cpp', - 'Foo.h', - False, include_state)) - self.assertEqual(cpp_style._MOC_HEADER, - classify_include('foo.cpp', - 'foo.moc', - False, include_state)) - self.assertEqual(cpp_style._MOC_HEADER, - classify_include('foo.cpp', - 'moc_foo.cpp', - False, include_state)) - # Tricky example where both includes might be classified as primary. - self.assert_language_rules_check('ScrollbarThemeWince.cpp', - '#include "config.h"\n' - '#include "ScrollbarThemeWince.h"\n' - '\n' - '#include "Scrollbar.h"\n', - '') - self.assert_language_rules_check('ScrollbarThemeWince.cpp', - '#include "config.h"\n' - '#include "Scrollbar.h"\n' - '\n' - '#include "ScrollbarThemeWince.h"\n', - 'Found header this file implements after a header this file implements.' - ' Should be: config.h, primary header, blank line, and then alphabetically sorted.' - ' [build/include_order] [4]') - self.assert_language_rules_check('ResourceHandleWin.cpp', - '#include "config.h"\n' - '#include "ResourceHandle.h"\n' - '\n' - '#include "ResourceHandleWin.h"\n', - '') - - def test_try_drop_common_suffixes(self): - self.assertEqual('foo/foo', cpp_style._drop_common_suffixes('foo/foo-inl.h')) - self.assertEqual('foo/bar/foo', - cpp_style._drop_common_suffixes('foo/bar/foo_inl.h')) - self.assertEqual('foo/foo', cpp_style._drop_common_suffixes('foo/foo.cpp')) - self.assertEqual('foo/foo_unusualinternal', - cpp_style._drop_common_suffixes('foo/foo_unusualinternal.h')) - self.assertEqual('', - cpp_style._drop_common_suffixes('_test.cpp')) - self.assertEqual('test', - cpp_style._drop_common_suffixes('test.cpp')) - self.assertEqual('test', - cpp_style._drop_common_suffixes('test.cpp')) - -class CheckForFunctionLengthsTest(CppStyleTestBase): - def setUp(self): - # Reducing these thresholds for the tests speeds up tests significantly. - self.old_normal_trigger = cpp_style._FunctionState._NORMAL_TRIGGER - self.old_test_trigger = cpp_style._FunctionState._TEST_TRIGGER - - cpp_style._FunctionState._NORMAL_TRIGGER = 10 - cpp_style._FunctionState._TEST_TRIGGER = 25 - - def tearDown(self): - cpp_style._FunctionState._NORMAL_TRIGGER = self.old_normal_trigger - cpp_style._FunctionState._TEST_TRIGGER = self.old_test_trigger - - # FIXME: Eliminate the need for this function. - def set_min_confidence(self, min_confidence): - """Set new test confidence and return old test confidence.""" - old_min_confidence = self.min_confidence - self.min_confidence = min_confidence - return old_min_confidence - - def assert_function_lengths_check(self, code, expected_message): - """Check warnings for long function bodies are as expected. - - Args: - code: C++ source code expected to generate a warning message. - expected_message: Message expected to be generated by the C++ code. - """ - self.assertEquals(expected_message, - self.perform_function_lengths_check(code)) - - def trigger_lines(self, error_level): - """Return number of lines needed to trigger a function length warning. - - Args: - error_level: --v setting for cpp_style. - - Returns: - Number of lines needed to trigger a function length warning. - """ - return cpp_style._FunctionState._NORMAL_TRIGGER * 2 ** error_level - - def trigger_test_lines(self, error_level): - """Return number of lines needed to trigger a test function length warning. - - Args: - error_level: --v setting for cpp_style. - - Returns: - Number of lines needed to trigger a test function length warning. - """ - return cpp_style._FunctionState._TEST_TRIGGER * 2 ** error_level - - def assert_function_length_check_definition(self, lines, error_level): - """Generate long function definition and check warnings are as expected. - - Args: - lines: Number of lines to generate. - error_level: --v setting for cpp_style. - """ - trigger_level = self.trigger_lines(self.min_confidence) - self.assert_function_lengths_check( - 'void test(int x)' + self.function_body(lines), - ('Small and focused functions are preferred: ' - 'test() has %d non-comment lines ' - '(error triggered by exceeding %d lines).' - ' [readability/fn_size] [%d]' - % (lines, trigger_level, error_level))) - - def assert_function_length_check_definition_ok(self, lines): - """Generate shorter function definition and check no warning is produced. - - Args: - lines: Number of lines to generate. - """ - self.assert_function_lengths_check( - 'void test(int x)' + self.function_body(lines), - '') - - def assert_function_length_check_at_error_level(self, error_level): - """Generate and check function at the trigger level for --v setting. - - Args: - error_level: --v setting for cpp_style. - """ - self.assert_function_length_check_definition(self.trigger_lines(error_level), - error_level) - - def assert_function_length_check_below_error_level(self, error_level): - """Generate and check function just below the trigger level for --v setting. - - Args: - error_level: --v setting for cpp_style. - """ - self.assert_function_length_check_definition(self.trigger_lines(error_level) - 1, - error_level - 1) - - def assert_function_length_check_above_error_level(self, error_level): - """Generate and check function just above the trigger level for --v setting. - - Args: - error_level: --v setting for cpp_style. - """ - self.assert_function_length_check_definition(self.trigger_lines(error_level) + 1, - error_level) - - def function_body(self, number_of_lines): - return ' {\n' + ' this_is_just_a_test();\n' * number_of_lines + '}' - - def function_body_with_blank_lines(self, number_of_lines): - return ' {\n' + ' this_is_just_a_test();\n\n' * number_of_lines + '}' - - def function_body_with_no_lints(self, number_of_lines): - return ' {\n' + ' this_is_just_a_test(); // NOLINT\n' * number_of_lines + '}' - - # Test line length checks. - def test_function_length_check_declaration(self): - self.assert_function_lengths_check( - 'void test();', # Not a function definition - '') - - def test_function_length_check_declaration_with_block_following(self): - self.assert_function_lengths_check( - ('void test();\n' - + self.function_body(66)), # Not a function definition - '') - - def test_function_length_check_class_definition(self): - self.assert_function_lengths_check( # Not a function definition - 'class Test' + self.function_body(66) + ';', - '') - - def test_function_length_check_trivial(self): - self.assert_function_lengths_check( - 'void test() {}', # Not counted - '') - - def test_function_length_check_empty(self): - self.assert_function_lengths_check( - 'void test() {\n}', - '') - - def test_function_length_check_definition_below_severity0(self): - old_min_confidence = self.set_min_confidence(0) - self.assert_function_length_check_definition_ok(self.trigger_lines(0) - 1) - self.set_min_confidence(old_min_confidence) - - def test_function_length_check_definition_at_severity0(self): - old_min_confidence = self.set_min_confidence(0) - self.assert_function_length_check_definition_ok(self.trigger_lines(0)) - self.set_min_confidence(old_min_confidence) - - def test_function_length_check_definition_above_severity0(self): - old_min_confidence = self.set_min_confidence(0) - self.assert_function_length_check_above_error_level(0) - self.set_min_confidence(old_min_confidence) - - def test_function_length_check_definition_below_severity1v0(self): - old_min_confidence = self.set_min_confidence(0) - self.assert_function_length_check_below_error_level(1) - self.set_min_confidence(old_min_confidence) - - def test_function_length_check_definition_at_severity1v0(self): - old_min_confidence = self.set_min_confidence(0) - self.assert_function_length_check_at_error_level(1) - self.set_min_confidence(old_min_confidence) - - def test_function_length_check_definition_below_severity1(self): - self.assert_function_length_check_definition_ok(self.trigger_lines(1) - 1) - - def test_function_length_check_definition_at_severity1(self): - self.assert_function_length_check_definition_ok(self.trigger_lines(1)) - - def test_function_length_check_definition_above_severity1(self): - self.assert_function_length_check_above_error_level(1) - - def test_function_length_check_definition_severity1_plus_indented(self): - error_level = 1 - error_lines = self.trigger_lines(error_level) + 1 - trigger_level = self.trigger_lines(self.min_confidence) - indent_spaces = ' ' - self.assert_function_lengths_check( - re.sub(r'(?m)^(.)', indent_spaces + r'\1', - 'void test_indent(int x)\n' + self.function_body(error_lines)), - ('Small and focused functions are preferred: ' - 'test_indent() has %d non-comment lines ' - '(error triggered by exceeding %d lines).' - ' [readability/fn_size] [%d]') - % (error_lines, trigger_level, error_level)) - - def test_function_length_check_definition_severity1_plus_blanks(self): - error_level = 1 - error_lines = self.trigger_lines(error_level) + 1 - trigger_level = self.trigger_lines(self.min_confidence) - self.assert_function_lengths_check( - 'void test_blanks(int x)' + self.function_body(error_lines), - ('Small and focused functions are preferred: ' - 'test_blanks() has %d non-comment lines ' - '(error triggered by exceeding %d lines).' - ' [readability/fn_size] [%d]') - % (error_lines, trigger_level, error_level)) - - def test_function_length_check_complex_definition_severity1(self): - error_level = 1 - error_lines = self.trigger_lines(error_level) + 1 - trigger_level = self.trigger_lines(self.min_confidence) - self.assert_function_lengths_check( - ('my_namespace::my_other_namespace::MyVeryLongTypeName<Type1, bool func(const Element*)>*\n' - 'my_namespace::my_other_namespace<Type3, Type4>::~MyFunction<Type5<Type6, Type7> >(int arg1, char* arg2)' - + self.function_body(error_lines)), - ('Small and focused functions are preferred: ' - 'my_namespace::my_other_namespace<Type3, Type4>::~MyFunction<Type5<Type6, Type7> >()' - ' has %d non-comment lines ' - '(error triggered by exceeding %d lines).' - ' [readability/fn_size] [%d]') - % (error_lines, trigger_level, error_level)) - - def test_function_length_check_definition_severity1_for_test(self): - error_level = 1 - error_lines = self.trigger_test_lines(error_level) + 1 - trigger_level = self.trigger_test_lines(self.min_confidence) - self.assert_function_lengths_check( - 'TEST_F(Test, Mutator)' + self.function_body(error_lines), - ('Small and focused functions are preferred: ' - 'TEST_F(Test, Mutator) has %d non-comment lines ' - '(error triggered by exceeding %d lines).' - ' [readability/fn_size] [%d]') - % (error_lines, trigger_level, error_level)) - - def test_function_length_check_definition_severity1_for_split_line_test(self): - error_level = 1 - error_lines = self.trigger_test_lines(error_level) + 1 - trigger_level = self.trigger_test_lines(self.min_confidence) - self.assert_function_lengths_check( - ('TEST_F(GoogleUpdateRecoveryRegistryProtectedTest,\n' - ' FixGoogleUpdate_AllValues_MachineApp)' # note: 4 spaces - + self.function_body(error_lines)), - ('Small and focused functions are preferred: ' - 'TEST_F(GoogleUpdateRecoveryRegistryProtectedTest, ' # 1 space - 'FixGoogleUpdate_AllValues_MachineApp) has %d non-comment lines ' - '(error triggered by exceeding %d lines).' - ' [readability/fn_size] [%d]') - % (error_lines, trigger_level, error_level)) - - def test_function_length_check_definition_severity1_for_bad_test_doesnt_break(self): - error_level = 1 - error_lines = self.trigger_test_lines(error_level) + 1 - trigger_level = self.trigger_test_lines(self.min_confidence) - self.assert_function_lengths_check( - ('TEST_F(' - + self.function_body(error_lines)), - ('Small and focused functions are preferred: ' - 'TEST_F has %d non-comment lines ' - '(error triggered by exceeding %d lines).' - ' [readability/fn_size] [%d]') - % (error_lines, trigger_level, error_level)) - - def test_function_length_check_definition_severity1_with_embedded_no_lints(self): - error_level = 1 - error_lines = self.trigger_lines(error_level) + 1 - trigger_level = self.trigger_lines(self.min_confidence) - self.assert_function_lengths_check( - 'void test(int x)' + self.function_body_with_no_lints(error_lines), - ('Small and focused functions are preferred: ' - 'test() has %d non-comment lines ' - '(error triggered by exceeding %d lines).' - ' [readability/fn_size] [%d]') - % (error_lines, trigger_level, error_level)) - - def test_function_length_check_definition_severity1_with_no_lint(self): - self.assert_function_lengths_check( - ('void test(int x)' + self.function_body(self.trigger_lines(1)) - + ' // NOLINT -- long function'), - '') - - def test_function_length_check_definition_below_severity2(self): - self.assert_function_length_check_below_error_level(2) - - def test_function_length_check_definition_severity2(self): - self.assert_function_length_check_at_error_level(2) - - def test_function_length_check_definition_above_severity2(self): - self.assert_function_length_check_above_error_level(2) - - def test_function_length_check_definition_below_severity3(self): - self.assert_function_length_check_below_error_level(3) - - def test_function_length_check_definition_severity3(self): - self.assert_function_length_check_at_error_level(3) - - def test_function_length_check_definition_above_severity3(self): - self.assert_function_length_check_above_error_level(3) - - def test_function_length_check_definition_below_severity4(self): - self.assert_function_length_check_below_error_level(4) - - def test_function_length_check_definition_severity4(self): - self.assert_function_length_check_at_error_level(4) - - def test_function_length_check_definition_above_severity4(self): - self.assert_function_length_check_above_error_level(4) - - def test_function_length_check_definition_below_severity5(self): - self.assert_function_length_check_below_error_level(5) - - def test_function_length_check_definition_at_severity5(self): - self.assert_function_length_check_at_error_level(5) - - def test_function_length_check_definition_above_severity5(self): - self.assert_function_length_check_above_error_level(5) - - def test_function_length_check_definition_huge_lines(self): - # 5 is the limit - self.assert_function_length_check_definition(self.trigger_lines(10), 5) - - def test_function_length_not_determinable(self): - # Macro invocation without terminating semicolon. - self.assert_function_lengths_check( - 'MACRO(arg)', - '') - - # Macro with underscores - self.assert_function_lengths_check( - 'MACRO_WITH_UNDERSCORES(arg1, arg2, arg3)', - '') - - self.assert_function_lengths_check( - 'NonMacro(arg)', - 'Lint failed to find start of function body.' - ' [readability/fn_size] [5]') - - -class NoNonVirtualDestructorsTest(CppStyleTestBase): - - def test_no_error(self): - self.assert_multi_line_lint( - '''class Foo { - virtual ~Foo(); - virtual void foo(); - };''', - '') - - self.assert_multi_line_lint( - '''class Foo { - virtual inline ~Foo(); - virtual void foo(); - };''', - '') - - self.assert_multi_line_lint( - '''class Foo { - inline virtual ~Foo(); - virtual void foo(); - };''', - '') - - self.assert_multi_line_lint( - '''class Foo::Goo { - virtual ~Goo(); - virtual void goo(); - };''', - '') - self.assert_multi_line_lint( - 'class Foo { void foo(); };', - 'More than one command on the same line [whitespace/newline] [4]') - self.assert_multi_line_lint( - 'class MyClass {\n' - ' int getIntValue() { ASSERT(m_ptr); return *m_ptr; }\n' - '};\n', - '') - self.assert_multi_line_lint( - 'class MyClass {\n' - ' int getIntValue()\n' - ' {\n' - ' ASSERT(m_ptr); return *m_ptr;\n' - ' }\n' - '};\n', - 'More than one command on the same line [whitespace/newline] [4]') - - self.assert_multi_line_lint( - '''class Qualified::Goo : public Foo { - virtual void goo(); - };''', - '') - - self.assert_multi_line_lint( - # Line-ending : - '''class Goo : - public Foo { - virtual void goo(); - };''', - 'Labels should always be indented at least one space. If this is a ' - 'member-initializer list in a constructor, the colon should be on the ' - 'line after the definition header. [whitespace/labels] [4]') - - def test_no_destructor_when_virtual_needed(self): - self.assert_multi_line_lint_re( - '''class Foo { - virtual void foo(); - };''', - 'The class Foo probably needs a virtual destructor') - - def test_destructor_non_virtual_when_virtual_needed(self): - self.assert_multi_line_lint_re( - '''class Foo { - ~Foo(); - virtual void foo(); - };''', - 'The class Foo probably needs a virtual destructor') - - def test_no_warn_when_derived(self): - self.assert_multi_line_lint( - '''class Foo : public Goo { - virtual void foo(); - };''', - '') - - def test_internal_braces(self): - self.assert_multi_line_lint_re( - '''class Foo { - enum Goo { - GOO - }; - virtual void foo(); - };''', - 'The class Foo probably needs a virtual destructor') - - def test_inner_class_needs_virtual_destructor(self): - self.assert_multi_line_lint_re( - '''class Foo { - class Goo { - virtual void goo(); - }; - };''', - 'The class Goo probably needs a virtual destructor') - - def test_outer_class_needs_virtual_destructor(self): - self.assert_multi_line_lint_re( - '''class Foo { - class Goo { - }; - virtual void foo(); - };''', - 'The class Foo probably needs a virtual destructor') - - def test_qualified_class_needs_virtual_destructor(self): - self.assert_multi_line_lint_re( - '''class Qualified::Foo { - virtual void foo(); - };''', - 'The class Qualified::Foo probably needs a virtual destructor') - - def test_multi_line_declaration_no_error(self): - self.assert_multi_line_lint_re( - '''class Foo - : public Goo { - virtual void foo(); - };''', - '') - - def test_multi_line_declaration_with_error(self): - self.assert_multi_line_lint( - '''class Foo - { - virtual void foo(); - };''', - ['This { should be at the end of the previous line ' - '[whitespace/braces] [4]', - 'The class Foo probably needs a virtual destructor due to having ' - 'virtual method(s), one declared at line 2. [runtime/virtual] [4]']) - - -class PassPtrTest(CppStyleTestBase): - # For http://webkit.org/coding/RefPtr.html - - def assert_pass_ptr_check(self, code, expected_message): - """Check warnings for Pass*Ptr are as expected. - - Args: - code: C++ source code expected to generate a warning message. - expected_message: Message expected to be generated by the C++ code. - """ - self.assertEquals(expected_message, - self.perform_pass_ptr_check(code)) - - def test_pass_ref_ptr_in_function(self): - # Local variables should never be PassRefPtr. - self.assert_pass_ptr_check( - 'int myFunction()\n' - '{\n' - ' PassRefPtr<Type1> variable = variable2;\n' - '}', - 'Local variables should never be PassRefPtr (see ' - 'http://webkit.org/coding/RefPtr.html). [readability/pass_ptr] [5]') - - def test_pass_own_ptr_in_function(self): - # Local variables should never be PassRefPtr. - self.assert_pass_ptr_check( - 'int myFunction()\n' - '{\n' - ' PassOwnPtr<Type1> variable = variable2;\n' - '}', - 'Local variables should never be PassOwnPtr (see ' - 'http://webkit.org/coding/RefPtr.html). [readability/pass_ptr] [5]') - - def test_pass_other_type_ptr_in_function(self): - # Local variables should never be PassRefPtr. - self.assert_pass_ptr_check( - 'int myFunction()\n' - '{\n' - ' PassOtherTypePtr<Type1> variable;\n' - '}', - 'Local variables should never be PassOtherTypePtr (see ' - 'http://webkit.org/coding/RefPtr.html). [readability/pass_ptr] [5]') - - def test_pass_ref_ptr_return_value(self): - self.assert_pass_ptr_check( - 'PassRefPtr<Type1>\n' - 'myFunction(int)\n' - '{\n' - '}', - '') - - def test_pass_ref_ptr_parameter_value(self): - self.assert_pass_ptr_check( - 'int myFunction(PassRefPtr<Type1>)\n' - '{\n' - '}', - '') - - -class WebKitStyleTest(CppStyleTestBase): - - # for http://webkit.org/coding/coding-style.html - def test_indentation(self): - # 1. Use spaces, not tabs. Tabs should only appear in files that - # require them for semantic meaning, like Makefiles. - self.assert_multi_line_lint( - 'class Foo {\n' - ' int goo;\n' - '};', - '') - self.assert_multi_line_lint( - 'class Foo {\n' - '\tint goo;\n' - '};', - 'Tab found; better to use spaces [whitespace/tab] [1]') - - # 2. The indent size is 4 spaces. - self.assert_multi_line_lint( - 'class Foo {\n' - ' int goo;\n' - '};', - '') - self.assert_multi_line_lint( - 'class Foo {\n' - ' int goo;\n' - '};', - 'Weird number of spaces at line-start. Are you using a 4-space indent? [whitespace/indent] [3]') - # FIXME: No tests for 8-spaces. - - # 3. In a header, code inside a namespace should not be indented. - self.assert_multi_line_lint( - 'namespace WebCore {\n\n' - 'class Document {\n' - ' int myVariable;\n' - '};\n' - '}', - '', - 'foo.h') - self.assert_multi_line_lint( - 'namespace OuterNamespace {\n' - ' namespace InnerNamespace {\n' - ' class Document {\n' - '};\n' - '};\n' - '}', - 'Code inside a namespace should not be indented. [whitespace/indent] [4]', - 'foo.h') - self.assert_multi_line_lint( - 'namespace OuterNamespace {\n' - ' class Document {\n' - ' namespace InnerNamespace {\n' - '};\n' - '};\n' - '}', - 'Code inside a namespace should not be indented. [whitespace/indent] [4]', - 'foo.h') - self.assert_multi_line_lint( - 'namespace WebCore {\n' - '#if 0\n' - ' class Document {\n' - '};\n' - '#endif\n' - '}', - 'Code inside a namespace should not be indented. [whitespace/indent] [4]', - 'foo.h') - self.assert_multi_line_lint( - 'namespace WebCore {\n' - 'class Document {\n' - '};\n' - '}', - '', - 'foo.h') - - # 4. In an implementation file (files with the extension .cpp, .c - # or .mm), code inside a namespace should not be indented. - self.assert_multi_line_lint( - 'namespace WebCore {\n\n' - 'Document::Foo()\n' - ' : foo(bar)\n' - ' , boo(far)\n' - '{\n' - ' stuff();\n' - '}', - '', - 'foo.cpp') - self.assert_multi_line_lint( - 'namespace OuterNamespace {\n' - 'namespace InnerNamespace {\n' - 'Document::Foo() { }\n' - ' void* p;\n' - '}\n' - '}\n', - 'Code inside a namespace should not be indented. [whitespace/indent] [4]', - 'foo.cpp') - self.assert_multi_line_lint( - 'namespace OuterNamespace {\n' - 'namespace InnerNamespace {\n' - 'Document::Foo() { }\n' - '}\n' - ' void* p;\n' - '}\n', - 'Code inside a namespace should not be indented. [whitespace/indent] [4]', - 'foo.cpp') - self.assert_multi_line_lint( - 'namespace WebCore {\n\n' - ' const char* foo = "start:;"\n' - ' "dfsfsfs";\n' - '}\n', - 'Code inside a namespace should not be indented. [whitespace/indent] [4]', - 'foo.cpp') - self.assert_multi_line_lint( - 'namespace WebCore {\n\n' - 'const char* foo(void* a = ";", // ;\n' - ' void* b);\n' - ' void* p;\n' - '}\n', - 'Code inside a namespace should not be indented. [whitespace/indent] [4]', - 'foo.cpp') - self.assert_multi_line_lint( - 'namespace WebCore {\n\n' - 'const char* foo[] = {\n' - ' "void* b);", // ;\n' - ' "asfdf",\n' - ' }\n' - ' void* p;\n' - '}\n', - 'Code inside a namespace should not be indented. [whitespace/indent] [4]', - 'foo.cpp') - self.assert_multi_line_lint( - 'namespace WebCore {\n\n' - 'const char* foo[] = {\n' - ' "void* b);", // }\n' - ' "asfdf",\n' - ' }\n' - '}\n', - '', - 'foo.cpp') - self.assert_multi_line_lint( - ' namespace WebCore {\n\n' - ' void Document::Foo()\n' - ' {\n' - 'start: // infinite loops are fun!\n' - ' goto start;\n' - ' }', - 'namespace should never be indented. [whitespace/indent] [4]', - 'foo.cpp') - self.assert_multi_line_lint( - 'namespace WebCore {\n' - ' Document::Foo() { }\n' - '}', - 'Code inside a namespace should not be indented.' - ' [whitespace/indent] [4]', - 'foo.cpp') - self.assert_multi_line_lint( - 'namespace WebCore {\n' - '#define abc(x) x; \\\n' - ' x\n' - '}', - '', - 'foo.cpp') - self.assert_multi_line_lint( - 'namespace WebCore {\n' - '#define abc(x) x; \\\n' - ' x\n' - ' void* x;' - '}', - 'Code inside a namespace should not be indented. [whitespace/indent] [4]', - 'foo.cpp') - - # 5. A case label should line up with its switch statement. The - # case statement is indented. - self.assert_multi_line_lint( - ' switch (condition) {\n' - ' case fooCondition:\n' - ' case barCondition:\n' - ' i++;\n' - ' break;\n' - ' default:\n' - ' i--;\n' - ' }\n', - '') - self.assert_multi_line_lint( - ' switch (condition) {\n' - ' case fooCondition:\n' - ' switch (otherCondition) {\n' - ' default:\n' - ' return;\n' - ' }\n' - ' default:\n' - ' i--;\n' - ' }\n', - '') - self.assert_multi_line_lint( - ' switch (condition) {\n' - ' case fooCondition: break;\n' - ' default: return;\n' - ' }\n', - '') - self.assert_multi_line_lint( - ' switch (condition) {\n' - ' case fooCondition:\n' - ' case barCondition:\n' - ' i++;\n' - ' break;\n' - ' default:\n' - ' i--;\n' - ' }\n', - 'A case label should not be indented, but line up with its switch statement.' - ' [whitespace/indent] [4]') - self.assert_multi_line_lint( - ' switch (condition) {\n' - ' case fooCondition:\n' - ' break;\n' - ' default:\n' - ' i--;\n' - ' }\n', - 'A case label should not be indented, but line up with its switch statement.' - ' [whitespace/indent] [4]') - self.assert_multi_line_lint( - ' switch (condition) {\n' - ' case fooCondition:\n' - ' case barCondition:\n' - ' switch (otherCondition) {\n' - ' default:\n' - ' return;\n' - ' }\n' - ' default:\n' - ' i--;\n' - ' }\n', - 'A case label should not be indented, but line up with its switch statement.' - ' [whitespace/indent] [4]') - self.assert_multi_line_lint( - ' switch (condition) {\n' - ' case fooCondition:\n' - ' case barCondition:\n' - ' i++;\n' - ' break;\n\n' - ' default:\n' - ' i--;\n' - ' }\n', - 'Non-label code inside switch statements should be indented.' - ' [whitespace/indent] [4]') - self.assert_multi_line_lint( - ' switch (condition) {\n' - ' case fooCondition:\n' - ' case barCondition:\n' - ' switch (otherCondition) {\n' - ' default:\n' - ' return;\n' - ' }\n' - ' default:\n' - ' i--;\n' - ' }\n', - 'Non-label code inside switch statements should be indented.' - ' [whitespace/indent] [4]') - - # 6. Boolean expressions at the same nesting level that span - # multiple lines should have their operators on the left side of - # the line instead of the right side. - self.assert_multi_line_lint( - ' return attr->name() == srcAttr\n' - ' || attr->name() == lowsrcAttr;\n', - '') - self.assert_multi_line_lint( - ' return attr->name() == srcAttr ||\n' - ' attr->name() == lowsrcAttr;\n', - 'Boolean expressions that span multiple lines should have their ' - 'operators on the left side of the line instead of the right side.' - ' [whitespace/operators] [4]') - - def test_spacing(self): - # 1. Do not place spaces around unary operators. - self.assert_multi_line_lint( - 'i++;', - '') - self.assert_multi_line_lint( - 'i ++;', - 'Extra space for operator ++; [whitespace/operators] [4]') - - # 2. Do place spaces around binary and ternary operators. - self.assert_multi_line_lint( - 'y = m * x + b;', - '') - self.assert_multi_line_lint( - 'f(a, b);', - '') - self.assert_multi_line_lint( - 'c = a | b;', - '') - self.assert_multi_line_lint( - 'return condition ? 1 : 0;', - '') - self.assert_multi_line_lint( - 'y=m*x+b;', - 'Missing spaces around = [whitespace/operators] [4]') - self.assert_multi_line_lint( - 'f(a,b);', - 'Missing space after , [whitespace/comma] [3]') - self.assert_multi_line_lint( - 'c = a|b;', - 'Missing spaces around | [whitespace/operators] [3]') - # FIXME: We cannot catch this lint error. - # self.assert_multi_line_lint( - # 'return condition ? 1:0;', - # '') - - # 3. Place spaces between control statements and their parentheses. - self.assert_multi_line_lint( - ' if (condition)\n' - ' doIt();\n', - '') - self.assert_multi_line_lint( - ' if(condition)\n' - ' doIt();\n', - 'Missing space before ( in if( [whitespace/parens] [5]') - - # 4. Do not place spaces between a function and its parentheses, - # or between a parenthesis and its content. - self.assert_multi_line_lint( - 'f(a, b);', - '') - self.assert_multi_line_lint( - 'f (a, b);', - 'Extra space before ( in function call [whitespace/parens] [4]') - self.assert_multi_line_lint( - 'f( a, b );', - ['Extra space after ( in function call [whitespace/parens] [4]', - 'Extra space before ) [whitespace/parens] [2]']) - - def test_line_breaking(self): - # 1. Each statement should get its own line. - self.assert_multi_line_lint( - ' x++;\n' - ' y++;\n' - ' if (condition);\n' - ' doIt();\n', - '') - self.assert_multi_line_lint( - ' if (condition) \\\n' - ' doIt();\n', - '') - self.assert_multi_line_lint( - ' x++; y++;', - 'More than one command on the same line [whitespace/newline] [4]') - self.assert_multi_line_lint( - ' if (condition) doIt();\n', - 'More than one command on the same line in if [whitespace/parens] [4]') - - # 2. An else statement should go on the same line as a preceding - # close brace if one is present, else it should line up with the - # if statement. - self.assert_multi_line_lint( - 'if (condition) {\n' - ' doSomething();\n' - ' doSomethingAgain();\n' - '} else {\n' - ' doSomethingElse();\n' - ' doSomethingElseAgain();\n' - '}\n', - '') - self.assert_multi_line_lint( - 'if (condition)\n' - ' doSomething();\n' - 'else\n' - ' doSomethingElse();\n', - '') - self.assert_multi_line_lint( - 'if (condition)\n' - ' doSomething();\n' - 'else {\n' - ' doSomethingElse();\n' - ' doSomethingElseAgain();\n' - '}\n', - '') - self.assert_multi_line_lint( - '#define TEST_ASSERT(expression) do { if (!(expression)) { TestsController::shared().testFailed(__FILE__, __LINE__, #expression); return; } } while (0)\n', - '') - self.assert_multi_line_lint( - '#define TEST_ASSERT(expression) do { if ( !(expression)) { TestsController::shared().testFailed(__FILE__, __LINE__, #expression); return; } } while (0)\n', - 'Extra space after ( in if [whitespace/parens] [5]') - # FIXME: currently we only check first conditional, so we cannot detect errors in next ones. - # self.assert_multi_line_lint( - # '#define TEST_ASSERT(expression) do { if (!(expression)) { TestsController::shared().testFailed(__FILE__, __LINE__, #expression); return; } } while (0 )\n', - # 'Mismatching spaces inside () in if [whitespace/parens] [5]') - self.assert_multi_line_lint( - 'if (condition) {\n' - ' doSomething();\n' - ' doSomethingAgain();\n' - '}\n' - 'else {\n' - ' doSomethingElse();\n' - ' doSomethingElseAgain();\n' - '}\n', - 'An else should appear on the same line as the preceding } [whitespace/newline] [4]') - self.assert_multi_line_lint( - 'if (condition) doSomething(); else doSomethingElse();\n', - ['More than one command on the same line [whitespace/newline] [4]', - 'Else clause should never be on same line as else (use 2 lines) [whitespace/newline] [4]', - 'More than one command on the same line in if [whitespace/parens] [4]']) - self.assert_multi_line_lint( - 'if (condition) doSomething(); else {\n' - ' doSomethingElse();\n' - '}\n', - ['More than one command on the same line in if [whitespace/parens] [4]', - 'One line control clauses should not use braces. [whitespace/braces] [4]']) - self.assert_multi_line_lint( - 'void func()\n' - '{\n' - ' while (condition) { }\n' - ' return 0;\n' - '}\n', - '') - self.assert_multi_line_lint( - 'void func()\n' - '{\n' - ' for (i = 0; i < 42; i++) { foobar(); }\n' - ' return 0;\n' - '}\n', - 'More than one command on the same line in for [whitespace/parens] [4]') - - # 3. An else if statement should be written as an if statement - # when the prior if concludes with a return statement. - self.assert_multi_line_lint( - 'if (motivated) {\n' - ' if (liquid)\n' - ' return money;\n' - '} else if (tired)\n' - ' break;\n', - '') - self.assert_multi_line_lint( - 'if (condition)\n' - ' doSomething();\n' - 'else if (otherCondition)\n' - ' doSomethingElse();\n', - '') - self.assert_multi_line_lint( - 'if (condition)\n' - ' doSomething();\n' - 'else\n' - ' doSomethingElse();\n', - '') - self.assert_multi_line_lint( - 'if (condition)\n' - ' returnValue = foo;\n' - 'else if (otherCondition)\n' - ' returnValue = bar;\n', - '') - self.assert_multi_line_lint( - 'if (condition)\n' - ' returnValue = foo;\n' - 'else\n' - ' returnValue = bar;\n', - '') - self.assert_multi_line_lint( - 'if (condition)\n' - ' doSomething();\n' - 'else if (liquid)\n' - ' return money;\n' - 'else if (broke)\n' - ' return favor;\n' - 'else\n' - ' sleep(28800);\n', - '') - self.assert_multi_line_lint( - 'if (liquid) {\n' - ' prepare();\n' - ' return money;\n' - '} else if (greedy) {\n' - ' keep();\n' - ' return nothing;\n' - '}\n', - 'An else if statement should be written as an if statement when the ' - 'prior "if" concludes with a return, break, continue or goto statement.' - ' [readability/control_flow] [4]') - self.assert_multi_line_lint( - ' if (stupid) {\n' - 'infiniteLoop:\n' - ' goto infiniteLoop;\n' - ' } else if (evil)\n' - ' goto hell;\n', - 'An else if statement should be written as an if statement when the ' - 'prior "if" concludes with a return, break, continue or goto statement.' - ' [readability/control_flow] [4]') - self.assert_multi_line_lint( - 'if (liquid)\n' - '{\n' - ' prepare();\n' - ' return money;\n' - '}\n' - 'else if (greedy)\n' - ' keep();\n', - ['This { should be at the end of the previous line [whitespace/braces] [4]', - 'An else should appear on the same line as the preceding } [whitespace/newline] [4]', - 'An else if statement should be written as an if statement when the ' - 'prior "if" concludes with a return, break, continue or goto statement.' - ' [readability/control_flow] [4]']) - self.assert_multi_line_lint( - 'if (gone)\n' - ' return;\n' - 'else if (here)\n' - ' go();\n', - 'An else if statement should be written as an if statement when the ' - 'prior "if" concludes with a return, break, continue or goto statement.' - ' [readability/control_flow] [4]') - self.assert_multi_line_lint( - 'if (gone)\n' - ' return;\n' - 'else\n' - ' go();\n', - 'An else statement can be removed when the prior "if" concludes ' - 'with a return, break, continue or goto statement.' - ' [readability/control_flow] [4]') - self.assert_multi_line_lint( - 'if (motivated) {\n' - ' prepare();\n' - ' continue;\n' - '} else {\n' - ' cleanUp();\n' - ' break;\n' - '}\n', - 'An else statement can be removed when the prior "if" concludes ' - 'with a return, break, continue or goto statement.' - ' [readability/control_flow] [4]') - self.assert_multi_line_lint( - 'if (tired)\n' - ' break;\n' - 'else {\n' - ' prepare();\n' - ' continue;\n' - '}\n', - 'An else statement can be removed when the prior "if" concludes ' - 'with a return, break, continue or goto statement.' - ' [readability/control_flow] [4]') - - def test_braces(self): - # 1. Function definitions: place each brace on its own line. - self.assert_multi_line_lint( - 'int main()\n' - '{\n' - ' doSomething();\n' - '}\n', - '') - self.assert_multi_line_lint( - 'int main() {\n' - ' doSomething();\n' - '}\n', - 'Place brace on its own line for function definitions. [whitespace/braces] [4]') - - # 2. Other braces: place the open brace on the line preceding the - # code block; place the close brace on its own line. - self.assert_multi_line_lint( - 'class MyClass {\n' - ' int foo;\n' - '};\n', - '') - self.assert_multi_line_lint( - 'namespace WebCore {\n' - 'int foo;\n' - '};\n', - '') - self.assert_multi_line_lint( - 'for (int i = 0; i < 10; i++) {\n' - ' DoSomething();\n' - '};\n', - '') - self.assert_multi_line_lint( - 'class MyClass\n' - '{\n' - ' int foo;\n' - '};\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'if (condition)\n' - '{\n' - ' int foo;\n' - '}\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'for (int i = 0; i < 10; i++)\n' - '{\n' - ' int foo;\n' - '}\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'while (true)\n' - '{\n' - ' int foo;\n' - '}\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'foreach (Foo* foo, foos)\n' - '{\n' - ' int bar;\n' - '}\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'switch (type)\n' - '{\n' - 'case foo: return;\n' - '}\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'if (condition)\n' - '{\n' - ' int foo;\n' - '}\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'for (int i = 0; i < 10; i++)\n' - '{\n' - ' int foo;\n' - '}\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'while (true)\n' - '{\n' - ' int foo;\n' - '}\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'switch (type)\n' - '{\n' - 'case foo: return;\n' - '}\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - self.assert_multi_line_lint( - 'else if (type)\n' - '{\n' - 'case foo: return;\n' - '}\n', - 'This { should be at the end of the previous line [whitespace/braces] [4]') - - # 3. One-line control clauses should not use braces unless - # comments are included or a single statement spans multiple - # lines. - self.assert_multi_line_lint( - 'if (true) {\n' - ' int foo;\n' - '}\n', - 'One line control clauses should not use braces. [whitespace/braces] [4]') - - self.assert_multi_line_lint( - 'for (; foo; bar) {\n' - ' int foo;\n' - '}\n', - 'One line control clauses should not use braces. [whitespace/braces] [4]') - - self.assert_multi_line_lint( - 'foreach (foo, foos) {\n' - ' int bar;\n' - '}\n', - 'One line control clauses should not use braces. [whitespace/braces] [4]') - - self.assert_multi_line_lint( - 'while (true) {\n' - ' int foo;\n' - '}\n', - 'One line control clauses should not use braces. [whitespace/braces] [4]') - - self.assert_multi_line_lint( - 'if (true)\n' - ' int foo;\n' - 'else {\n' - ' int foo;\n' - '}\n', - 'One line control clauses should not use braces. [whitespace/braces] [4]') - - self.assert_multi_line_lint( - 'if (true) {\n' - ' int foo;\n' - '} else\n' - ' int foo;\n', - 'One line control clauses should not use braces. [whitespace/braces] [4]') - - self.assert_multi_line_lint( - 'if (true) {\n' - ' // Some comment\n' - ' int foo;\n' - '}\n', - '') - - self.assert_multi_line_lint( - 'if (true) {\n' - ' myFunction(reallyLongParam1, reallyLongParam2,\n' - ' reallyLongParam3);\n' - '}\n', - '') - - # 4. Control clauses without a body should use empty braces. - self.assert_multi_line_lint( - 'for ( ; current; current = current->next) { }\n', - '') - self.assert_multi_line_lint( - 'for ( ; current;\n' - ' current = current->next) {}\n', - '') - self.assert_multi_line_lint( - 'for ( ; current; current = current->next);\n', - 'Semicolon defining empty statement for this loop. Use { } instead. [whitespace/semicolon] [5]') - self.assert_multi_line_lint( - 'while (true);\n', - 'Semicolon defining empty statement for this loop. Use { } instead. [whitespace/semicolon] [5]') - self.assert_multi_line_lint( - '} while (true);\n', - '') - - def test_null_false_zero(self): - # 1. In C++, the null pointer value should be written as 0. In C, - # it should be written as NULL. In Objective-C and Objective-C++, - # follow the guideline for C or C++, respectively, but use nil to - # represent a null Objective-C object. - self.assert_lint( - 'functionCall(NULL)', - 'Use 0 instead of NULL.' - ' [readability/null] [5]', - 'foo.cpp') - self.assert_lint( - "// Don't use NULL in comments since it isn't in code.", - 'Use 0 instead of NULL.' - ' [readability/null] [4]', - 'foo.cpp') - self.assert_lint( - '"A string with NULL" // and a comment with NULL is tricky to flag correctly in cpp_style.', - 'Use 0 instead of NULL.' - ' [readability/null] [4]', - 'foo.cpp') - self.assert_lint( - '"A string containing NULL is ok"', - '', - 'foo.cpp') - self.assert_lint( - 'if (aboutNULL)', - '', - 'foo.cpp') - self.assert_lint( - 'myVariable = NULLify', - '', - 'foo.cpp') - # Make sure that the NULL check does not apply to C and Objective-C files. - self.assert_lint( - 'functionCall(NULL)', - '', - 'foo.c') - self.assert_lint( - 'functionCall(NULL)', - '', - 'foo.m') - - # Make sure that the NULL check does not apply to g_object_{set,get} and - # g_str{join,concat} - self.assert_lint( - 'g_object_get(foo, "prop", &bar, NULL);', - '') - self.assert_lint( - 'g_object_set(foo, "prop", bar, NULL);', - '') - self.assert_lint( - 'g_build_filename(foo, bar, NULL);', - '') - self.assert_lint( - 'gst_bin_add_many(foo, bar, boo, NULL);', - '') - self.assert_lint( - 'gst_bin_remove_many(foo, bar, boo, NULL);', - '') - self.assert_lint( - 'gst_element_link_many(foo, bar, boo, NULL);', - '') - self.assert_lint( - 'gst_element_unlink_many(foo, bar, boo, NULL);', - '') - self.assert_lint( - 'gchar* result = g_strconcat("part1", "part2", "part3", NULL);', - '') - self.assert_lint( - 'gchar* result = g_strconcat("part1", NULL);', - '') - self.assert_lint( - 'gchar* result = g_strjoin(",", "part1", "part2", "part3", NULL);', - '') - self.assert_lint( - 'gchar* result = g_strjoin(",", "part1", NULL);', - '') - self.assert_lint( - 'gchar* result = gdk_pixbuf_save_to_callback(pixbuf, function, data, type, error, NULL);', - '') - self.assert_lint( - 'gchar* result = gdk_pixbuf_save_to_buffer(pixbuf, function, data, type, error, NULL);', - '') - self.assert_lint( - 'gchar* result = gdk_pixbuf_save_to_stream(pixbuf, function, data, type, error, NULL);', - '') - - # 2. C++ and C bool values should be written as true and - # false. Objective-C BOOL values should be written as YES and NO. - # FIXME: Implement this. - - # 3. Tests for true/false, null/non-null, and zero/non-zero should - # all be done without equality comparisons. - self.assert_lint( - 'if (count == 0)', - 'Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.' - ' [readability/comparison_to_zero] [5]') - self.assert_lint_one_of_many_errors_re( - 'if (string != NULL)', - r'Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons\.') - self.assert_lint( - 'if (condition == true)', - 'Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.' - ' [readability/comparison_to_zero] [5]') - self.assert_lint( - 'if (myVariable != /* Why would anyone put a comment here? */ false)', - 'Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.' - ' [readability/comparison_to_zero] [5]') - - self.assert_lint( - 'if (0 /* This comment also looks odd to me. */ != aLongerVariableName)', - 'Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.' - ' [readability/comparison_to_zero] [5]') - self.assert_lint_one_of_many_errors_re( - 'if (NULL == thisMayBeNull)', - r'Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons\.') - self.assert_lint( - 'if (true != anotherCondition)', - 'Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.' - ' [readability/comparison_to_zero] [5]') - self.assert_lint( - 'if (false == myBoolValue)', - 'Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.' - ' [readability/comparison_to_zero] [5]') - - self.assert_lint( - 'if (fontType == trueType)', - '') - self.assert_lint( - 'if (othertrue == fontType)', - '') - - def test_using_std(self): - self.assert_lint( - 'using std::min;', - "Use 'using namespace std;' instead of 'using std::min;'." - " [build/using_std] [4]", - 'foo.cpp') - - def test_max_macro(self): - self.assert_lint( - 'int i = MAX(0, 1);', - '', - 'foo.c') - - self.assert_lint( - 'int i = MAX(0, 1);', - 'Use std::max() or std::max<type>() instead of the MAX() macro.' - ' [runtime/max_min_macros] [4]', - 'foo.cpp') - - self.assert_lint( - 'inline int foo() { return MAX(0, 1); }', - 'Use std::max() or std::max<type>() instead of the MAX() macro.' - ' [runtime/max_min_macros] [4]', - 'foo.h') - - def test_min_macro(self): - self.assert_lint( - 'int i = MIN(0, 1);', - '', - 'foo.c') - - self.assert_lint( - 'int i = MIN(0, 1);', - 'Use std::min() or std::min<type>() instead of the MIN() macro.' - ' [runtime/max_min_macros] [4]', - 'foo.cpp') - - self.assert_lint( - 'inline int foo() { return MIN(0, 1); }', - 'Use std::min() or std::min<type>() instead of the MIN() macro.' - ' [runtime/max_min_macros] [4]', - 'foo.h') - - def test_names(self): - name_underscore_error_message = " is incorrectly named. Don't use underscores in your identifier names. [readability/naming] [4]" - name_tooshort_error_message = " is incorrectly named. Don't use the single letter 'l' as an identifier name. [readability/naming] [4]" - - # Basic cases from WebKit style guide. - self.assert_lint('struct Data;', '') - self.assert_lint('size_t bufferSize;', '') - self.assert_lint('class HTMLDocument;', '') - self.assert_lint('String mimeType();', '') - self.assert_lint('size_t buffer_size;', - 'buffer_size' + name_underscore_error_message) - self.assert_lint('short m_length;', '') - self.assert_lint('short _length;', - '_length' + name_underscore_error_message) - self.assert_lint('short length_;', - 'length_' + name_underscore_error_message) - self.assert_lint('unsigned _length;', - '_length' + name_underscore_error_message) - self.assert_lint('unsigned int _length;', - '_length' + name_underscore_error_message) - self.assert_lint('unsigned long long _length;', - '_length' + name_underscore_error_message) - - # Variable name 'l' is easy to confuse with '1' - self.assert_lint('int l;', 'l' + name_tooshort_error_message) - self.assert_lint('size_t l;', 'l' + name_tooshort_error_message) - self.assert_lint('long long l;', 'l' + name_tooshort_error_message) - - # Pointers, references, functions, templates, and adjectives. - self.assert_lint('char* under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('const int UNDER_SCORE;', - 'UNDER_SCORE' + name_underscore_error_message) - self.assert_lint('static inline const char const& const under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('WebCore::RenderObject* under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('int func_name();', - 'func_name' + name_underscore_error_message) - self.assert_lint('RefPtr<RenderObject*> under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('WTF::Vector<WTF::RefPtr<const RenderObject* const> > under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('int under_score[];', - 'under_score' + name_underscore_error_message) - self.assert_lint('struct dirent* under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('long under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('long long under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('long double under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('long long int under_score;', - 'under_score' + name_underscore_error_message) - - # Declarations in control statement. - self.assert_lint('if (int under_score = 42) {', - 'under_score' + name_underscore_error_message) - self.assert_lint('else if (int under_score = 42) {', - 'under_score' + name_underscore_error_message) - self.assert_lint('for (int under_score = 42; cond; i++) {', - 'under_score' + name_underscore_error_message) - self.assert_lint('while (foo & under_score = bar) {', - 'under_score' + name_underscore_error_message) - self.assert_lint('for (foo * under_score = p; cond; i++) {', - 'under_score' + name_underscore_error_message) - self.assert_lint('for (foo * under_score; cond; i++) {', - 'under_score' + name_underscore_error_message) - self.assert_lint('while (foo & value_in_thirdparty_library) {', '') - self.assert_lint('while (foo * value_in_thirdparty_library) {', '') - self.assert_lint('if (mli && S_OK == mli->foo()) {', '') - - # More member variables and functions. - self.assert_lint('int SomeClass::s_validName', '') - self.assert_lint('int m_under_score;', - 'm_under_score' + name_underscore_error_message) - self.assert_lint('int SomeClass::s_under_score = 0;', - 'SomeClass::s_under_score' + name_underscore_error_message) - self.assert_lint('int SomeClass::under_score = 0;', - 'SomeClass::under_score' + name_underscore_error_message) - - # Other statements. - self.assert_lint('return INT_MAX;', '') - self.assert_lint('return_t under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('goto under_score;', - 'under_score' + name_underscore_error_message) - self.assert_lint('delete static_cast<Foo*>(p);', '') - - # Multiple variables in one line. - self.assert_lint('void myFunction(int variable1, int another_variable);', - 'another_variable' + name_underscore_error_message) - self.assert_lint('int variable1, another_variable;', - 'another_variable' + name_underscore_error_message) - self.assert_lint('int first_variable, secondVariable;', - 'first_variable' + name_underscore_error_message) - self.assert_lint('void my_function(int variable_1, int variable_2);', - ['my_function' + name_underscore_error_message, - 'variable_1' + name_underscore_error_message, - 'variable_2' + name_underscore_error_message]) - self.assert_lint('for (int variable_1, variable_2;;) {', - ['variable_1' + name_underscore_error_message, - 'variable_2' + name_underscore_error_message]) - - # There is an exception for op code functions but only in the JavaScriptCore directory. - self.assert_lint('void this_op_code(int var1, int var2)', '', 'JavaScriptCore/foo.cpp') - self.assert_lint('void op_code(int var1, int var2)', '', 'JavaScriptCore/foo.cpp') - self.assert_lint('void this_op_code(int var1, int var2)', 'this_op_code' + name_underscore_error_message) - - # GObject requires certain magical names in class declarations. - self.assert_lint('void webkit_dom_object_init();', '') - self.assert_lint('void webkit_dom_object_class_init();', '') - - # There is an exception for some unit tests that begin with "tst_". - self.assert_lint('void tst_QWebFrame::arrayObjectEnumerable(int var1, int var2)', '') - - # The Qt API uses names that begin with "qt_". - self.assert_lint('void QTFrame::qt_drt_is_awesome(int var1, int var2)', '') - self.assert_lint('void qt_drt_is_awesome(int var1, int var2);', '') - - # Cairo forward-declarations should not be a failure. - self.assert_lint('typedef struct _cairo cairo_t;', '') - self.assert_lint('typedef struct _cairo_surface cairo_surface_t;', '') - self.assert_lint('typedef struct _cairo_scaled_font cairo_scaled_font_t;', '') - - # NPAPI functions that start with NPN_, NPP_ or NP_ are allowed. - self.assert_lint('void NPN_Status(NPP, const char*)', '') - self.assert_lint('NPError NPP_SetWindow(NPP instance, NPWindow *window)', '') - self.assert_lint('NPObject* NP_Allocate(NPP, NPClass*)', '') - - # const_iterator is allowed as well. - self.assert_lint('typedef VectorType::const_iterator const_iterator;', '') - - # vm_throw is allowed as well. - self.assert_lint('int vm_throw;', '') - - # Bitfields. - self.assert_lint('unsigned _fillRule : 1;', - '_fillRule' + name_underscore_error_message) - - # new operators in initialization. - self.assert_lint('OwnPtr<uint32_t> variable(new uint32_t);', '') - self.assert_lint('OwnPtr<uint32_t> variable(new (expr) uint32_t);', '') - self.assert_lint('OwnPtr<uint32_t> under_score(new uint32_t);', - 'under_score' + name_underscore_error_message) - - - def test_comments(self): - # A comment at the beginning of a line is ok. - self.assert_lint('// comment', '') - self.assert_lint(' // comment', '') - - self.assert_lint('} // namespace WebCore', - 'One space before end of line comments' - ' [whitespace/comments] [5]') - - def test_other(self): - # FIXME: Implement this. - pass - - -class CppCheckerTest(unittest.TestCase): - - """Tests CppChecker class.""" - - def mock_handle_style_error(self): - pass - - def _checker(self): - return CppChecker("foo", "h", self.mock_handle_style_error, 3) - - def test_init(self): - """Test __init__ constructor.""" - checker = self._checker() - self.assertEquals(checker.file_extension, "h") - self.assertEquals(checker.file_path, "foo") - self.assertEquals(checker.handle_style_error, self.mock_handle_style_error) - self.assertEquals(checker.min_confidence, 3) - - def test_eq(self): - """Test __eq__ equality function.""" - checker1 = self._checker() - checker2 = self._checker() - - # == calls __eq__. - self.assertTrue(checker1 == checker2) - - def mock_handle_style_error2(self): - pass - - # Verify that a difference in any argument cause equality to fail. - checker = CppChecker("foo", "h", self.mock_handle_style_error, 3) - self.assertFalse(checker == CppChecker("bar", "h", self.mock_handle_style_error, 3)) - self.assertFalse(checker == CppChecker("foo", "c", self.mock_handle_style_error, 3)) - self.assertFalse(checker == CppChecker("foo", "h", mock_handle_style_error2, 3)) - self.assertFalse(checker == CppChecker("foo", "h", self.mock_handle_style_error, 4)) - - def test_ne(self): - """Test __ne__ inequality function.""" - checker1 = self._checker() - checker2 = self._checker() - - # != calls __ne__. - # By default, __ne__ always returns true on different objects. - # Thus, just check the distinguishing case to verify that the - # code defines __ne__. - self.assertFalse(checker1 != checker2) - - -def tearDown(): - """A global check to make sure all error-categories have been tested. - - The main tearDown() routine is the only code we can guarantee will be - run after all other tests have been executed. - """ - try: - if _run_verifyallcategoriesseen: - ErrorCollector(None).verify_all_categories_are_seen() - except NameError: - # If nobody set the global _run_verifyallcategoriesseen, then - # we assume we shouldn't run the test - pass - -if __name__ == '__main__': - import sys - # We don't want to run the verify_all_categories_are_seen() test unless - # we're running the full test suite: if we only run one test, - # obviously we're not going to see all the error categories. So we - # only run verify_all_categories_are_seen() when no commandline flags - # are passed in. - global _run_verifyallcategoriesseen - _run_verifyallcategoriesseen = (len(sys.argv) == 1) - - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/python.py b/WebKitTools/Scripts/webkitpy/style/checkers/python.py deleted file mode 100644 index 70d4450..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/python.py +++ /dev/null @@ -1,56 +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 checking WebKit style in Python files.""" - -from ...style_references import pep8 - - -class PythonChecker(object): - - """Processes text lines for checking style.""" - - def __init__(self, file_path, handle_style_error): - self._file_path = file_path - self._handle_style_error = handle_style_error - - def check(self, lines): - # Initialize pep8.options, which is necessary for - # Checker.check_all() to execute. - pep8.process_options(arglist=[self._file_path]) - - checker = pep8.Checker(self._file_path) - - def _pep8_handle_error(line_number, offset, text, check): - # FIXME: Incorporate the character offset into the error output. - # This will require updating the error handler __call__ - # signature to include an optional "offset" parameter. - pep8_code = text[:4] - pep8_message = text[5:] - - category = "pep8/" + pep8_code - - self._handle_style_error(line_number, category, 5, pep8_message) - - checker.report_error = _pep8_handle_error - - errors = checker.check_all() diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/python_unittest.py b/WebKitTools/Scripts/webkitpy/style/checkers/python_unittest.py deleted file mode 100644 index e003eb8..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/python_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 python.py.""" - -import os -import unittest - -from python import PythonChecker - - -class PythonCheckerTest(unittest.TestCase): - - """Tests the PythonChecker class.""" - - def test_init(self): - """Test __init__() method.""" - def _mock_handle_style_error(self): - pass - - checker = PythonChecker("foo.txt", _mock_handle_style_error) - self.assertEquals(checker._file_path, "foo.txt") - self.assertEquals(checker._handle_style_error, - _mock_handle_style_error) - - def test_check(self): - """Test check() method.""" - errors = [] - - def _mock_handle_style_error(line_number, category, confidence, - message): - error = (line_number, category, confidence, message) - errors.append(error) - - current_dir = os.path.dirname(__file__) - file_path = os.path.join(current_dir, "python_unittest_input.py") - - checker = PythonChecker(file_path, _mock_handle_style_error) - checker.check(lines=[]) - - self.assertEquals(len(errors), 1) - self.assertEquals(errors[0], - (2, "pep8/W291", 5, "trailing whitespace")) diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/python_unittest_input.py b/WebKitTools/Scripts/webkitpy/style/checkers/python_unittest_input.py deleted file mode 100644 index 9f1d118..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/python_unittest_input.py +++ /dev/null @@ -1,2 +0,0 @@ -# This file is sample input for python_unittest.py and includes a single -# error which is an extra space at the end of this line. diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py deleted file mode 100644 index d2d67f3..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py +++ /dev/null @@ -1,123 +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. - -"""Checks WebKit style for test_expectations files.""" - -import logging -import os -import re -import sys - -from common import TabChecker -from webkitpy.style_references import port -from webkitpy.style_references import test_expectations - -_log = logging.getLogger("webkitpy.style.checkers.test_expectations") - - -class ChromiumOptions(object): - """A mock object for creating chromium port object. - - port.get() requires an options object which has 'chromium' attribute to create - chromium port object for each platform. This class mocks such object. - """ - def __init__(self): - self.chromium = True - self.use_drt = True - - -class TestExpectationsChecker(object): - """Processes test_expectations.txt lines for validating the syntax.""" - - categories = set(['test/expectations']) - - def __init__(self, file_path, handle_style_error): - self._file_path = file_path - self._handle_style_error = handle_style_error - self._tab_checker = TabChecker(file_path, handle_style_error) - self._output_regex = re.compile('Line:(?P<line>\d+)\s*(?P<message>.+)') - # Determining the port of this expectations. - try: - port_name = self._file_path.split(os.sep)[-2] - if port_name == "chromium": - options = ChromiumOptions() - self._port_obj = port.get(port_name=None, options=options) - else: - self._port_obj = port.get(port_name=port_name) - except: - # Using 'test' port when we couldn't determine the port for this - # expectations. - _log.warn("Could not determine the port for %s. " - "Using 'test' port, but platform-specific expectations " - "will fail the check." % self._file_path) - self._port_obj = port.get('test') - self._port_to_check = self._port_obj.test_platform_name() - # Suppress error messages of test_expectations module since they will be - # reported later. - log = logging.getLogger("webkitpy.layout_tests.layout_package." - "test_expectations") - log.setLevel(logging.CRITICAL) - - def _handle_error_message(self, lineno, message, confidence): - pass - - def check_test_expectations(self, expectations_str, tests=None, overrides=None): - errors = [] - expectations = None - try: - expectations = test_expectations.TestExpectationsFile( - port=self._port_obj, expectations=expectations_str, full_test_list=tests, - test_platform_name=self._port_to_check, is_debug_mode=False, - is_lint_mode=True, suppress_errors=False, overrides=overrides) - except SyntaxError, error: - errors = str(error).splitlines() - - for error in errors: - matched = self._output_regex.match(error) - if matched: - lineno, message = matched.group('line', 'message') - self._handle_style_error(int(lineno), 'test/expectations', 5, message) - - if expectations: - for error in expectations.get_non_fatal_errors(): - matched = self._output_regex.match(error) - if matched: - lineno, message = matched.group('line', 'message') - self._handle_style_error(int(lineno), 'test/expectations', 2, message) - - def check_tabs(self, lines): - self._tab_checker.check(lines) - - def check(self, lines): - overrides = self._port_obj.test_expectations_overrides() - expectations = '\n'.join(lines) - self.check_test_expectations(expectations_str=expectations, - tests=None, - overrides=overrides) - # Warn tabs in lines as well - self.check_tabs(lines) diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py deleted file mode 100644 index 31f0b40..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py +++ /dev/null @@ -1,168 +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 test_expectations.py.""" - -import os -import sys -import unittest - -# We need following workaround hack to run this unit tests in stand-alone. -try: - d = os.path.dirname(__file__) -except NameError: - d = os.path.dirname(sys.argv[0]) -sys.path.append(os.path.abspath(os.path.join(d, '../../../'))) - -from test_expectations import TestExpectationsChecker -from webkitpy.style_references import port -from webkitpy.style_references import test_expectations as test_expectations_style - - -class ErrorCollector(object): - """An error handler class for unit tests.""" - - def __init__(self): - self._errors = [] - - def __call__(self, lineno, category, confidence, message): - self._errors.append('%s [%s] [%d]' % (message, category, confidence)) - - def get_errors(self): - return ''.join(self._errors) - - def reset_errors(self): - self._errors = [] - - -class TestExpectationsTestCase(unittest.TestCase): - """TestCase for test_expectations.py""" - - def setUp(self): - self._error_collector = ErrorCollector() - port_obj = port.get('test') - self._test_file = os.path.join(port_obj.layout_tests_dir(), 'passes/text.html') - - def process_expectations(self, expectations, overrides=None): - self._checker = TestExpectationsChecker() - - def assert_lines_lint(self, lines, expected): - self._error_collector.reset_errors() - checker = TestExpectationsChecker('test/test_expectations.txt', - self._error_collector) - checker.check_test_expectations(expectations_str='\n'.join(lines), - tests=[self._test_file], - overrides=None) - checker.check_tabs(lines) - self.assertEqual(expected, self._error_collector.get_errors()) - - def test_valid_expectations(self): - self.assert_lines_lint( - ["passes/text.html = PASS"], - "") - self.assert_lines_lint( - ["passes/text.html = FAIL PASS"], - "") - self.assert_lines_lint( - ["passes/text.html = CRASH TIMEOUT FAIL PASS"], - "") - self.assert_lines_lint( - ["BUG1234 MAC : passes/text.html = PASS FAIL"], - "") - self.assert_lines_lint( - ["SKIP BUG1234 : passes/text.html = TIMEOUT PASS"], - "") - self.assert_lines_lint( - ["BUG1234 DEBUG : passes/text.html = TIMEOUT PASS"], - "") - self.assert_lines_lint( - ["BUG1234 DEBUG SKIP : passes/text.html = TIMEOUT PASS"], - "") - self.assert_lines_lint( - ["BUG1234 MAC DEBUG SKIP : passes/text.html = TIMEOUT PASS"], - "") - self.assert_lines_lint( - ["BUG1234 DEBUG MAC : passes/text.html = TIMEOUT PASS"], - "") - self.assert_lines_lint( - ["SLOW BUG1234 : passes/text.html = PASS"], - "") - self.assert_lines_lint( - ["WONTFIX SKIP : passes/text.html = TIMEOUT"], - "") - - def test_valid_modifiers(self): - self.assert_lines_lint( - ["INVALID-MODIFIER : passes/text.html = PASS"], - "Invalid modifier for test: invalid-modifier " - "passes/text.html [test/expectations] [5]") - self.assert_lines_lint( - ["SKIP : passes/text.html = PASS"], - "Test lacks BUG modifier. " - "passes/text.html [test/expectations] [2]") - - def test_expectation_errors(self): - self.assert_lines_lint( - ["missing expectations"], - "Missing expectations. ['missing expectations'] [test/expectations] [5]") - self.assert_lines_lint( - ["SLOW : passes/text.html = TIMEOUT"], - "A test can not be both slow and timeout. " - "If it times out indefinitely, then it should be just timeout. " - "passes/text.html [test/expectations] [5]") - self.assert_lines_lint( - ["does/not/exist.html = FAIL"], - "Path does not exist. does/not/exist.html [test/expectations] [2]") - - def test_parse_expectations(self): - self.assert_lines_lint( - ["passes/text.html = PASS"], - "") - self.assert_lines_lint( - ["passes/text.html = UNSUPPORTED"], - "Unsupported expectation: unsupported " - "passes/text.html [test/expectations] [5]") - self.assert_lines_lint( - ["passes/text.html = PASS UNSUPPORTED"], - "Unsupported expectation: unsupported " - "passes/text.html [test/expectations] [5]") - - def test_already_seen_test(self): - self.assert_lines_lint( - ["passes/text.html = PASS", - "passes/text.html = TIMEOUT"], - "Duplicate expectation. %s [test/expectations] [5]" % self._test_file) - - def test_tab(self): - self.assert_lines_lint( - ["\tpasses/text.html = PASS"], - "Line contains tab character. [whitespace/tab] [5]") - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/text.py b/WebKitTools/Scripts/webkitpy/style/checkers/text.py deleted file mode 100644 index 1147658..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/text.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (C) 2009 Google Inc. 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 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. - -"""Checks WebKit style for text files.""" - -from common import TabChecker - -class TextChecker(object): - - """Processes text lines for checking style.""" - - def __init__(self, file_path, handle_style_error): - self.file_path = file_path - self.handle_style_error = handle_style_error - self._tab_checker = TabChecker(file_path, handle_style_error) - - def check(self, lines): - self._tab_checker.check(lines) - - -# FIXME: Remove this function (requires refactoring unit tests). -def process_file_data(filename, lines, error): - checker = TextChecker(filename, error) - checker.check(lines) - diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/text_unittest.py b/WebKitTools/Scripts/webkitpy/style/checkers/text_unittest.py deleted file mode 100644 index ced49a9..0000000 --- a/WebKitTools/Scripts/webkitpy/style/checkers/text_unittest.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/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. - -"""Unit test for text_style.py.""" - -import unittest - -import text as text_style -from text import TextChecker - -class TextStyleTestCase(unittest.TestCase): - """TestCase for text_style.py""" - - def assertNoError(self, lines): - """Asserts that the specified lines has no errors.""" - self.had_error = False - - def error_for_test(line_number, category, confidence, message): - """Records if an error occurs.""" - self.had_error = True - - text_style.process_file_data('', lines, error_for_test) - self.assert_(not self.had_error, '%s should not have any errors.' % lines) - - def assertError(self, lines, expected_line_number): - """Asserts that the specified lines has an error.""" - self.had_error = False - - def error_for_test(line_number, category, confidence, message): - """Checks if the expected error occurs.""" - self.assertEquals(expected_line_number, line_number) - self.assertEquals('whitespace/tab', category) - self.had_error = True - - text_style.process_file_data('', lines, error_for_test) - self.assert_(self.had_error, '%s should have an error [whitespace/tab].' % lines) - - - def test_no_error(self): - """Tests for no error cases.""" - self.assertNoError(['']) - self.assertNoError(['abc def', 'ggg']) - - - def test_error(self): - """Tests for error cases.""" - self.assertError(['2009-12-16\tKent Tamura\t<tkent@chromium.org>'], 1) - self.assertError(['2009-12-16 Kent Tamura <tkent@chromium.org>', - '', - '\tReviewed by NOBODY.'], 3) - - -class TextCheckerTest(unittest.TestCase): - - """Tests TextChecker class.""" - - def mock_handle_style_error(self): - pass - - def test_init(self): - """Test __init__ constructor.""" - checker = TextChecker("foo.txt", self.mock_handle_style_error) - self.assertEquals(checker.file_path, "foo.txt") - self.assertEquals(checker.handle_style_error, self.mock_handle_style_error) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/style/error_handlers.py b/WebKitTools/Scripts/webkitpy/style/error_handlers.py deleted file mode 100644 index 0bede24..0000000 --- a/WebKitTools/Scripts/webkitpy/style/error_handlers.py +++ /dev/null @@ -1,159 +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. - -"""Defines style error handler classes. - -A style error handler is a function to call when a style error is -found. Style error handlers can also have state. A class that represents -a style error handler should implement the following methods. - -Methods: - - __call__(self, line_number, category, confidence, message): - - Handle the occurrence of a style error. - - Check whether the error is reportable. If so, increment the total - error count and report the details. Note that error reporting can - be suppressed after reaching a certain number of reports. - - Args: - line_number: The integer line number of the line containing the error. - category: The name of the category of the error, for example - "whitespace/newline". - confidence: An integer between 1 and 5 inclusive that represents the - application's level of confidence in the error. The value - 5 means that we are certain of the problem, and the - value 1 means that it could be a legitimate construct. - message: The error message to report. - -""" - - -import sys - - -class DefaultStyleErrorHandler(object): - - """The default style error handler.""" - - def __init__(self, file_path, configuration, increment_error_count, - line_numbers=None): - """Create a default style error handler. - - Args: - file_path: The path to the file containing the error. This - is used for reporting to the user. - configuration: A StyleProcessorConfiguration instance. - increment_error_count: A function that takes no arguments and - increments the total count of reportable - errors. - line_numbers: An array of line numbers of the lines for which - style errors should be reported, or None if errors - for all lines should be reported. When it is not - None, this array normally contains the line numbers - corresponding to the modified lines of a patch. - - """ - if line_numbers is not None: - line_numbers = set(line_numbers) - - self._file_path = file_path - self._configuration = configuration - self._increment_error_count = increment_error_count - self._line_numbers = line_numbers - - # A string to integer dictionary cache of the number of reportable - # errors per category passed to this instance. - self._category_totals = {} - - # Useful for unit testing. - def __eq__(self, other): - """Return whether this instance is equal to another.""" - if self._configuration != other._configuration: - return False - if self._file_path != other._file_path: - return False - if self._increment_error_count != other._increment_error_count: - return False - if self._line_numbers != other._line_numbers: - return False - - return True - - # Useful for unit testing. - def __ne__(self, other): - # Python does not automatically deduce __ne__ from __eq__. - return not self.__eq__(other) - - def _add_reportable_error(self, category): - """Increment the error count and return the new category total.""" - self._increment_error_count() # Increment the total. - - # Increment the category total. - if not category in self._category_totals: - self._category_totals[category] = 1 - else: - self._category_totals[category] += 1 - - return self._category_totals[category] - - def _max_reports(self, category): - """Return the maximum number of errors to report.""" - if not category in self._configuration.max_reports_per_category: - return None - return self._configuration.max_reports_per_category[category] - - def __call__(self, line_number, category, confidence, message): - """Handle the occurrence of a style error. - - See the docstring of this module for more information. - - """ - if (self._line_numbers is not None and - line_number not in self._line_numbers): - # Then the error occurred in a line that was not modified, so - # the error is not reportable. - return - - if not self._configuration.is_reportable(category=category, - confidence_in_error=confidence, - file_path=self._file_path): - return - - category_total = self._add_reportable_error(category) - - max_reports = self._max_reports(category) - - if (max_reports is not None) and (category_total > max_reports): - # Then suppress displaying the error. - return - - self._configuration.write_style_error(category=category, - confidence_in_error=confidence, - file_path=self._file_path, - line_number=line_number, - message=message) - - if category_total == max_reports: - self._configuration.stderr_write("Suppressing further [%s] reports " - "for this file.\n" % category) diff --git a/WebKitTools/Scripts/webkitpy/style/error_handlers_unittest.py b/WebKitTools/Scripts/webkitpy/style/error_handlers_unittest.py deleted file mode 100644 index 23619cc..0000000 --- a/WebKitTools/Scripts/webkitpy/style/error_handlers_unittest.py +++ /dev/null @@ -1,187 +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 error_handlers.py.""" - - -import unittest - -from checker import StyleProcessorConfiguration -from error_handlers import DefaultStyleErrorHandler -from filter import FilterConfiguration - - -class DefaultStyleErrorHandlerTest(unittest.TestCase): - - """Tests the DefaultStyleErrorHandler class.""" - - def setUp(self): - self._error_messages = [] - self._error_count = 0 - - _category = "whitespace/tab" - """The category name for the tests in this class.""" - - _file_path = "foo.h" - """The file path for the tests in this class.""" - - def _mock_increment_error_count(self): - self._error_count += 1 - - def _mock_stderr_write(self, message): - self._error_messages.append(message) - - def _style_checker_configuration(self): - """Return a StyleProcessorConfiguration instance for testing.""" - base_rules = ["-whitespace", "+whitespace/tab"] - filter_configuration = FilterConfiguration(base_rules=base_rules) - - return StyleProcessorConfiguration( - filter_configuration=filter_configuration, - max_reports_per_category={"whitespace/tab": 2}, - min_confidence=3, - output_format="vs7", - stderr_write=self._mock_stderr_write) - - def _error_handler(self, configuration, line_numbers=None): - return DefaultStyleErrorHandler(configuration=configuration, - file_path=self._file_path, - increment_error_count=self._mock_increment_error_count, - line_numbers=line_numbers) - - def _check_initialized(self): - """Check that count and error messages are initialized.""" - self.assertEquals(0, self._error_count) - self.assertEquals(0, len(self._error_messages)) - - def _call_error_handler(self, handle_error, confidence, line_number=100): - """Call the given error handler with a test error.""" - handle_error(line_number=line_number, - category=self._category, - confidence=confidence, - message="message") - - def test_eq__true_return_value(self): - """Test the __eq__() method for the return value of True.""" - handler1 = self._error_handler(configuration=None) - handler2 = self._error_handler(configuration=None) - - self.assertTrue(handler1.__eq__(handler2)) - - def test_eq__false_return_value(self): - """Test the __eq__() method for the return value of False.""" - def make_handler(configuration=self._style_checker_configuration(), - file_path='foo.txt', increment_error_count=lambda: True, - line_numbers=[100]): - return DefaultStyleErrorHandler(configuration=configuration, - file_path=file_path, - increment_error_count=increment_error_count, - line_numbers=line_numbers) - - handler = make_handler() - - # Establish a baseline for our comparisons below. - self.assertTrue(handler.__eq__(make_handler())) - - # Verify that a difference in any argument causes equality to fail. - self.assertFalse(handler.__eq__(make_handler(configuration=None))) - self.assertFalse(handler.__eq__(make_handler(file_path='bar.txt'))) - self.assertFalse(handler.__eq__(make_handler(increment_error_count=None))) - self.assertFalse(handler.__eq__(make_handler(line_numbers=[50]))) - - def test_ne(self): - """Test the __ne__() method.""" - # By default, __ne__ always returns true on different objects. - # Thus, check just the distinguishing case to verify that the - # code defines __ne__. - handler1 = self._error_handler(configuration=None) - handler2 = self._error_handler(configuration=None) - - self.assertFalse(handler1.__ne__(handler2)) - - def test_non_reportable_error(self): - """Test __call__() with a non-reportable error.""" - self._check_initialized() - configuration = self._style_checker_configuration() - - confidence = 1 - # Confirm the error is not reportable. - self.assertFalse(configuration.is_reportable(self._category, - confidence, - self._file_path)) - error_handler = self._error_handler(configuration) - self._call_error_handler(error_handler, confidence) - - self.assertEquals(0, self._error_count) - self.assertEquals([], self._error_messages) - - # Also serves as a reportable error test. - def test_max_reports_per_category(self): - """Test error report suppression in __call__() method.""" - self._check_initialized() - configuration = self._style_checker_configuration() - error_handler = self._error_handler(configuration) - - confidence = 5 - - # First call: usual reporting. - self._call_error_handler(error_handler, confidence) - self.assertEquals(1, self._error_count) - self.assertEquals(1, len(self._error_messages)) - self.assertEquals(self._error_messages, - ["foo.h(100): message [whitespace/tab] [5]\n"]) - - # Second call: suppression message reported. - self._call_error_handler(error_handler, confidence) - # The "Suppressing further..." message counts as an additional - # message (but not as an addition to the error count). - self.assertEquals(2, self._error_count) - self.assertEquals(3, len(self._error_messages)) - self.assertEquals(self._error_messages[-2], - "foo.h(100): message [whitespace/tab] [5]\n") - self.assertEquals(self._error_messages[-1], - "Suppressing further [whitespace/tab] reports " - "for this file.\n") - - # Third call: no report. - self._call_error_handler(error_handler, confidence) - self.assertEquals(3, self._error_count) - self.assertEquals(3, len(self._error_messages)) - - def test_line_numbers(self): - """Test the line_numbers parameter.""" - self._check_initialized() - configuration = self._style_checker_configuration() - error_handler = self._error_handler(configuration, - line_numbers=[50]) - confidence = 5 - - # Error on non-modified line: no error. - self._call_error_handler(error_handler, confidence, line_number=60) - self.assertEquals(0, self._error_count) - self.assertEquals([], self._error_messages) - - # Error on modified line: error. - self._call_error_handler(error_handler, confidence, line_number=50) - self.assertEquals(1, self._error_count) - self.assertEquals(self._error_messages, - ["foo.h(50): message [whitespace/tab] [5]\n"]) diff --git a/WebKitTools/Scripts/webkitpy/style/filereader.py b/WebKitTools/Scripts/webkitpy/style/filereader.py deleted file mode 100644 index 1a24cb5..0000000 --- a/WebKitTools/Scripts/webkitpy/style/filereader.py +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) -# Copyright (C) 2010 ProFUSION embedded systems -# -# 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. - -"""Supports reading and processing text files.""" - -import codecs -import logging -import os -import sys - - -_log = logging.getLogger(__name__) - - -class TextFileReader(object): - - """Supports reading and processing text files. - - Attributes: - file_count: The total number of files passed to this instance - for processing, including non-text files and files - that should be skipped. - delete_only_file_count: The total number of files that are not - processed this instance actually because - the files don't have any modified lines - but should be treated as processed. - - """ - - def __init__(self, processor): - """Create an instance. - - Arguments: - processor: A ProcessorBase instance. - - """ - self._processor = processor - self.file_count = 0 - self.delete_only_file_count = 0 - - def _read_lines(self, file_path): - """Read the file at a path, and return its lines. - - Raises: - IOError: If the file does not exist or cannot be read. - - """ - # Support the UNIX convention of using "-" for stdin. - if file_path == '-': - file = codecs.StreamReaderWriter(sys.stdin, - codecs.getreader('utf8'), - codecs.getwriter('utf8'), - 'replace') - else: - # We do not open the file with universal newline support - # (codecs does not support it anyway), so the resulting - # lines contain trailing "\r" characters if we are reading - # a file with CRLF endings. - file = codecs.open(file_path, 'r', 'utf8', 'replace') - - try: - contents = file.read() - finally: - file.close() - - lines = contents.split('\n') - return lines - - def process_file(self, file_path, **kwargs): - """Process the given file by calling the processor's process() method. - - Args: - file_path: The path of the file to process. - **kwargs: Any additional keyword parameters that should be passed - to the processor's process() method. The process() - method should support these keyword arguments. - - Raises: - SystemExit: If no file at file_path exists. - - """ - self.file_count += 1 - - if not os.path.exists(file_path) and file_path != "-": - _log.error("File does not exist: '%s'" % file_path) - sys.exit(1) - - if not self._processor.should_process(file_path): - _log.debug("Skipping file: '%s'" % file_path) - return - _log.debug("Processing file: '%s'" % file_path) - - try: - lines = self._read_lines(file_path) - except IOError, err: - message = ("Could not read file. Skipping: '%s'\n %s" - % (file_path, err)) - _log.warn(message) - return - - self._processor.process(lines, file_path, **kwargs) - - def _process_directory(self, directory): - """Process all files in the given directory, recursively. - - Args: - directory: A directory path. - - """ - for dir_path, dir_names, file_names in os.walk(directory): - for file_name in file_names: - file_path = os.path.join(dir_path, file_name) - self.process_file(file_path) - - def process_paths(self, paths): - """Process the given file and directory paths. - - Args: - paths: A list of file and directory paths. - - """ - for path in paths: - if os.path.isdir(path): - self._process_directory(directory=path) - else: - self.process_file(path) - - def count_delete_only_file(self): - """Count up files that contains only deleted lines. - - Files which has no modified or newly-added lines don't need - to check style, but should be treated as checked. For that - purpose, we just count up the number of such files. - """ - self.delete_only_file_count += 1 diff --git a/WebKitTools/Scripts/webkitpy/style/filereader_unittest.py b/WebKitTools/Scripts/webkitpy/style/filereader_unittest.py deleted file mode 100644 index 6328337..0000000 --- a/WebKitTools/Scripts/webkitpy/style/filereader_unittest.py +++ /dev/null @@ -1,166 +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 unit tests for filereader.py.""" - -from __future__ import with_statement - -import codecs -import os -import shutil -import tempfile -import unittest - -from webkitpy.common.system.logtesting import LoggingTestCase -from webkitpy.style.checker import ProcessorBase -from webkitpy.style.filereader import TextFileReader - - -class TextFileReaderTest(LoggingTestCase): - - class MockProcessor(ProcessorBase): - - """A processor for test purposes. - - This processor simply records the parameters passed to its process() - method for later checking by the unittest test methods. - - """ - - def __init__(self): - self.processed = [] - """The parameters passed for all calls to the process() method.""" - - def should_process(self, file_path): - return not file_path.endswith('should_not_process.txt') - - def process(self, lines, file_path, test_kwarg=None): - self.processed.append((lines, file_path, test_kwarg)) - - def setUp(self): - LoggingTestCase.setUp(self) - processor = TextFileReaderTest.MockProcessor() - - temp_dir = tempfile.mkdtemp() - - self._file_reader = TextFileReader(processor) - self._processor = processor - self._temp_dir = temp_dir - - def tearDown(self): - LoggingTestCase.tearDown(self) - shutil.rmtree(self._temp_dir) - - def _create_file(self, rel_path, text, encoding="utf-8"): - """Create a file with given text and return the path to the file.""" - # FIXME: There are better/more secure APIs for creatin tmp file paths. - file_path = os.path.join(self._temp_dir, rel_path) - with codecs.open(file_path, "w", encoding) as file: - file.write(text) - return file_path - - def _passed_to_processor(self): - """Return the parameters passed to MockProcessor.process().""" - return self._processor.processed - - def _assert_file_reader(self, passed_to_processor, file_count): - """Assert the state of the file reader.""" - self.assertEquals(passed_to_processor, self._passed_to_processor()) - self.assertEquals(file_count, self._file_reader.file_count) - - def test_process_file__does_not_exist(self): - try: - self._file_reader.process_file('does_not_exist.txt') - except SystemExit, err: - self.assertEquals(str(err), '1') - else: - self.fail('No Exception raised.') - self._assert_file_reader([], 1) - self.assertLog(["ERROR: File does not exist: 'does_not_exist.txt'\n"]) - - def test_process_file__is_dir(self): - temp_dir = os.path.join(self._temp_dir, 'test_dir') - os.mkdir(temp_dir) - - self._file_reader.process_file(temp_dir) - - # Because the log message below contains exception text, it is - # possible that the text varies across platforms. For this reason, - # we check only the portion of the log message that we control, - # namely the text at the beginning. - log_messages = self.logMessages() - # We remove the message we are looking at to prevent the tearDown() - # from raising an exception when it asserts that no log messages - # remain. - message = log_messages.pop() - - self.assertTrue(message.startswith('WARNING: Could not read file. ' - "Skipping: '%s'\n " % temp_dir)) - - self._assert_file_reader([], 1) - - def test_process_file__should_not_process(self): - file_path = self._create_file('should_not_process.txt', 'contents') - - self._file_reader.process_file(file_path) - self._assert_file_reader([], 1) - - def test_process_file__multiple_lines(self): - file_path = self._create_file('foo.txt', 'line one\r\nline two\n') - - self._file_reader.process_file(file_path) - processed = [(['line one\r', 'line two', ''], file_path, None)] - self._assert_file_reader(processed, 1) - - def test_process_file__file_stdin(self): - file_path = self._create_file('-', 'file contents') - - self._file_reader.process_file(file_path=file_path, test_kwarg='foo') - processed = [(['file contents'], file_path, 'foo')] - self._assert_file_reader(processed, 1) - - def test_process_file__with_kwarg(self): - file_path = self._create_file('foo.txt', 'file contents') - - self._file_reader.process_file(file_path=file_path, test_kwarg='foo') - processed = [(['file contents'], file_path, 'foo')] - self._assert_file_reader(processed, 1) - - def test_process_paths(self): - # We test a list of paths that contains both a file and a directory. - dir = os.path.join(self._temp_dir, 'foo_dir') - os.mkdir(dir) - - file_path1 = self._create_file('file1.txt', 'foo') - - rel_path = os.path.join('foo_dir', 'file2.txt') - file_path2 = self._create_file(rel_path, 'bar') - - self._file_reader.process_paths([dir, file_path1]) - processed = [(['bar'], file_path2, None), - (['foo'], file_path1, None)] - self._assert_file_reader(processed, 2) - - def test_count_delete_only_file(self): - self._file_reader.count_delete_only_file() - delete_only_file_count = self._file_reader.delete_only_file_count - self.assertEquals(delete_only_file_count, 1) diff --git a/WebKitTools/Scripts/webkitpy/style/filter.py b/WebKitTools/Scripts/webkitpy/style/filter.py deleted file mode 100644 index 608a9e6..0000000 --- a/WebKitTools/Scripts/webkitpy/style/filter.py +++ /dev/null @@ -1,278 +0,0 @@ -# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) -# -# 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 filter-related code.""" - - -def validate_filter_rules(filter_rules, all_categories): - """Validate the given filter rules, and raise a ValueError if not valid. - - Args: - filter_rules: A list of boolean filter rules, for example-- - ["-whitespace", "+whitespace/braces"] - all_categories: A list of all available category names, for example-- - ["whitespace/tabs", "whitespace/braces"] - - Raises: - ValueError: An error occurs if a filter rule does not begin - with "+" or "-" or if a filter rule does not match - the beginning of some category name in the list - of all available categories. - - """ - for rule in filter_rules: - if not (rule.startswith('+') or rule.startswith('-')): - raise ValueError('Invalid filter rule "%s": every rule ' - "must start with + or -." % rule) - - for category in all_categories: - if category.startswith(rule[1:]): - break - else: - raise ValueError('Suspected incorrect filter rule "%s": ' - "the rule does not match the beginning " - "of any category name." % rule) - - -class _CategoryFilter(object): - - """Filters whether to check style categories.""" - - def __init__(self, filter_rules=None): - """Create a category filter. - - Args: - filter_rules: A list of strings that are filter rules, which - are strings beginning with the plus or minus - symbol (+/-). The list should include any - default filter rules at the beginning. - Defaults to the empty list. - - Raises: - ValueError: Invalid filter rule if a rule does not start with - plus ("+") or minus ("-"). - - """ - if filter_rules is None: - filter_rules = [] - - self._filter_rules = filter_rules - self._should_check_category = {} # Cached dictionary of category to True/False - - def __str__(self): - return ",".join(self._filter_rules) - - # Useful for unit testing. - def __eq__(self, other): - """Return whether this CategoryFilter instance is equal to another.""" - return self._filter_rules == other._filter_rules - - # Useful for unit testing. - def __ne__(self, other): - # Python does not automatically deduce from __eq__(). - return not (self == other) - - def should_check(self, category): - """Return whether the category should be checked. - - The rules for determining whether a category should be checked - are as follows. By default all categories should be checked. - Then apply the filter rules in order from first to last, with - later flags taking precedence. - - A filter rule applies to a category if the string after the - leading plus/minus (+/-) matches the beginning of the category - name. A plus (+) means the category should be checked, while a - minus (-) means the category should not be checked. - - """ - if category in self._should_check_category: - return self._should_check_category[category] - - should_check = True # All categories checked by default. - for rule in self._filter_rules: - if not category.startswith(rule[1:]): - continue - should_check = rule.startswith('+') - self._should_check_category[category] = should_check # Update cache. - return should_check - - -class FilterConfiguration(object): - - """Supports filtering with path-specific and user-specified rules.""" - - def __init__(self, base_rules=None, path_specific=None, user_rules=None): - """Create a FilterConfiguration instance. - - Args: - base_rules: The starting list of filter rules to use for - processing. The default is the empty list, which - by itself would mean that all categories should be - checked. - - path_specific: A list of (sub_paths, path_rules) pairs - that stores the path-specific filter rules for - appending to the base rules. - The "sub_paths" value is a list of path - substrings. If a file path contains one of the - substrings, then the corresponding path rules - are appended. The first substring match takes - precedence, i.e. only the first match triggers - an append. - The "path_rules" value is a list of filter - rules that can be appended to the base rules. - - user_rules: A list of filter rules that is always appended - to the base rules and any path rules. In other - words, the user rules take precedence over the - everything. In practice, the user rules are - provided by the user from the command line. - - """ - if base_rules is None: - base_rules = [] - if path_specific is None: - path_specific = [] - if user_rules is None: - user_rules = [] - - self._base_rules = base_rules - self._path_specific = path_specific - self._path_specific_lower = None - """The backing store for self._get_path_specific_lower().""" - - self._user_rules = user_rules - - self._path_rules_to_filter = {} - """Cached dictionary of path rules to CategoryFilter instance.""" - - # The same CategoryFilter instance can be shared across - # multiple keys in this dictionary. This allows us to take - # greater advantage of the caching done by - # CategoryFilter.should_check(). - self._path_to_filter = {} - """Cached dictionary of file path to CategoryFilter instance.""" - - # Useful for unit testing. - def __eq__(self, other): - """Return whether this FilterConfiguration is equal to another.""" - if self._base_rules != other._base_rules: - return False - if self._path_specific != other._path_specific: - return False - if self._user_rules != other._user_rules: - return False - - return True - - # Useful for unit testing. - def __ne__(self, other): - # Python does not automatically deduce this from __eq__(). - return not self.__eq__(other) - - # We use the prefix "_get" since the name "_path_specific_lower" - # is already taken up by the data attribute backing store. - def _get_path_specific_lower(self): - """Return a copy of self._path_specific with the paths lower-cased.""" - if self._path_specific_lower is None: - self._path_specific_lower = [] - for (sub_paths, path_rules) in self._path_specific: - sub_paths = map(str.lower, sub_paths) - self._path_specific_lower.append((sub_paths, path_rules)) - return self._path_specific_lower - - def _path_rules_from_path(self, path): - """Determine the path-specific rules to use, and return as a tuple. - - This method returns a tuple rather than a list so the return - value can be passed to _filter_from_path_rules() without change. - - """ - path = path.lower() - for (sub_paths, path_rules) in self._get_path_specific_lower(): - for sub_path in sub_paths: - if path.find(sub_path) > -1: - return tuple(path_rules) - return () # Default to the empty tuple. - - def _filter_from_path_rules(self, path_rules): - """Return the CategoryFilter associated to the given path rules. - - Args: - path_rules: A tuple of path rules. We require a tuple rather - than a list so the value can be used as a dictionary - key in self._path_rules_to_filter. - - """ - # We reuse the same CategoryFilter where possible to take - # advantage of the caching they do. - if path_rules not in self._path_rules_to_filter: - rules = list(self._base_rules) # Make a copy - rules.extend(path_rules) - rules.extend(self._user_rules) - self._path_rules_to_filter[path_rules] = _CategoryFilter(rules) - - return self._path_rules_to_filter[path_rules] - - def _filter_from_path(self, path): - """Return the CategoryFilter associated to a path.""" - if path not in self._path_to_filter: - path_rules = self._path_rules_from_path(path) - filter = self._filter_from_path_rules(path_rules) - self._path_to_filter[path] = filter - - return self._path_to_filter[path] - - def should_check(self, category, path): - """Return whether the given category should be checked. - - This method determines whether a category should be checked - by checking the category name against the filter rules for - the given path. - - For a given path, the filter rules are the combination of - the base rules, the path-specific rules, and the user-provided - rules -- in that order. As we will describe below, later rules - in the list take precedence. The path-specific rules are the - rules corresponding to the first element of the "path_specific" - parameter that contains a string case-insensitively matching - some substring of the path. If there is no such element, - there are no path-specific rules for that path. - - Given a list of filter rules, the logic for determining whether - a category should be checked is as follows. By default all - categories should be checked. Then apply the filter rules in - order from first to last, with later flags taking precedence. - - A filter rule applies to a category if the string after the - leading plus/minus (+/-) matches the beginning of the category - name. A plus (+) means the category should be checked, while a - minus (-) means the category should not be checked. - - Args: - category: The category name. - path: The path of the file being checked. - - """ - return self._filter_from_path(path).should_check(category) - diff --git a/WebKitTools/Scripts/webkitpy/style/filter_unittest.py b/WebKitTools/Scripts/webkitpy/style/filter_unittest.py deleted file mode 100644 index 7b8a5402..0000000 --- a/WebKitTools/Scripts/webkitpy/style/filter_unittest.py +++ /dev/null @@ -1,256 +0,0 @@ -# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) -# -# 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 filter.py.""" - -import unittest - -from filter import _CategoryFilter as CategoryFilter -from filter import validate_filter_rules -from filter import FilterConfiguration - -# On Testing __eq__() and __ne__(): -# -# In the tests below, we deliberately do not use assertEquals() or -# assertNotEquals() to test __eq__() or __ne__(). We do this to be -# very explicit about what we are testing, especially in the case -# of assertNotEquals(). -# -# Part of the reason is that it is not immediately clear what -# expression the unittest module uses to assert "not equals" -- the -# negation of __eq__() or __ne__(), which are not necessarily -# equivalent expresions in Python. For example, from Python's "Data -# Model" documentation-- -# -# "There are no implied relationships among the comparison -# operators. The truth of x==y does not imply that x!=y is -# false. Accordingly, when defining __eq__(), one should -# also define __ne__() so that the operators will behave as -# expected." -# -# (from http://docs.python.org/reference/datamodel.html#object.__ne__ ) - -class ValidateFilterRulesTest(unittest.TestCase): - - """Tests validate_filter_rules() function.""" - - def test_validate_filter_rules(self): - all_categories = ["tabs", "whitespace", "build/include"] - - bad_rules = [ - "tabs", - "*tabs", - " tabs", - " +tabs", - "+whitespace/newline", - "+xxx", - ] - - good_rules = [ - "+tabs", - "-tabs", - "+build" - ] - - for rule in bad_rules: - self.assertRaises(ValueError, validate_filter_rules, - [rule], all_categories) - - for rule in good_rules: - # This works: no error. - validate_filter_rules([rule], all_categories) - - -class CategoryFilterTest(unittest.TestCase): - - """Tests CategoryFilter class.""" - - def test_init(self): - """Test __init__ method.""" - # Test that the attributes are getting set correctly. - filter = CategoryFilter(["+"]) - self.assertEquals(["+"], filter._filter_rules) - - def test_init_default_arguments(self): - """Test __init__ method default arguments.""" - filter = CategoryFilter() - self.assertEquals([], filter._filter_rules) - - def test_str(self): - """Test __str__ "to string" operator.""" - filter = CategoryFilter(["+a", "-b"]) - self.assertEquals(str(filter), "+a,-b") - - def test_eq(self): - """Test __eq__ equality function.""" - filter1 = CategoryFilter(["+a", "+b"]) - filter2 = CategoryFilter(["+a", "+b"]) - filter3 = CategoryFilter(["+b", "+a"]) - - # See the notes at the top of this module about testing - # __eq__() and __ne__(). - self.assertTrue(filter1.__eq__(filter2)) - self.assertFalse(filter1.__eq__(filter3)) - - def test_ne(self): - """Test __ne__ inequality function.""" - # By default, __ne__ always returns true on different objects. - # Thus, just check the distinguishing case to verify that the - # code defines __ne__. - # - # Also, see the notes at the top of this module about testing - # __eq__() and __ne__(). - self.assertFalse(CategoryFilter().__ne__(CategoryFilter())) - - def test_should_check(self): - """Test should_check() method.""" - filter = CategoryFilter() - self.assertTrue(filter.should_check("everything")) - # Check a second time to exercise cache. - self.assertTrue(filter.should_check("everything")) - - filter = CategoryFilter(["-"]) - self.assertFalse(filter.should_check("anything")) - # Check a second time to exercise cache. - self.assertFalse(filter.should_check("anything")) - - filter = CategoryFilter(["-", "+ab"]) - self.assertTrue(filter.should_check("abc")) - self.assertFalse(filter.should_check("a")) - - filter = CategoryFilter(["+", "-ab"]) - self.assertFalse(filter.should_check("abc")) - self.assertTrue(filter.should_check("a")) - - -class FilterConfigurationTest(unittest.TestCase): - - """Tests FilterConfiguration class.""" - - def _config(self, base_rules, path_specific, user_rules): - """Return a FilterConfiguration instance.""" - return FilterConfiguration(base_rules=base_rules, - path_specific=path_specific, - user_rules=user_rules) - - def test_init(self): - """Test __init__ method.""" - # Test that the attributes are getting set correctly. - # We use parameter values that are different from the defaults. - base_rules = ["-"] - path_specific = [(["path"], ["+a"])] - user_rules = ["+"] - - config = self._config(base_rules, path_specific, user_rules) - - self.assertEquals(base_rules, config._base_rules) - self.assertEquals(path_specific, config._path_specific) - self.assertEquals(user_rules, config._user_rules) - - def test_default_arguments(self): - # Test that the attributes are getting set correctly to the defaults. - config = FilterConfiguration() - - self.assertEquals([], config._base_rules) - self.assertEquals([], config._path_specific) - self.assertEquals([], config._user_rules) - - def test_eq(self): - """Test __eq__ method.""" - # See the notes at the top of this module about testing - # __eq__() and __ne__(). - self.assertTrue(FilterConfiguration().__eq__(FilterConfiguration())) - - # Verify that a difference in any argument causes equality to fail. - config = FilterConfiguration() - - # These parameter values are different from the defaults. - base_rules = ["-"] - path_specific = [(["path"], ["+a"])] - user_rules = ["+"] - - self.assertFalse(config.__eq__(FilterConfiguration( - base_rules=base_rules))) - self.assertFalse(config.__eq__(FilterConfiguration( - path_specific=path_specific))) - self.assertFalse(config.__eq__(FilterConfiguration( - user_rules=user_rules))) - - def test_ne(self): - """Test __ne__ method.""" - # By default, __ne__ always returns true on different objects. - # Thus, just check the distinguishing case to verify that the - # code defines __ne__. - # - # Also, see the notes at the top of this module about testing - # __eq__() and __ne__(). - self.assertFalse(FilterConfiguration().__ne__(FilterConfiguration())) - - def test_base_rules(self): - """Test effect of base_rules on should_check().""" - base_rules = ["-b"] - path_specific = [] - user_rules = [] - - config = self._config(base_rules, path_specific, user_rules) - - self.assertTrue(config.should_check("a", "path")) - self.assertFalse(config.should_check("b", "path")) - - def test_path_specific(self): - """Test effect of path_rules_specifier on should_check().""" - base_rules = ["-"] - path_specific = [(["path1"], ["+b"]), - (["path2"], ["+c"])] - user_rules = [] - - config = self._config(base_rules, path_specific, user_rules) - - self.assertFalse(config.should_check("c", "path1")) - self.assertTrue(config.should_check("c", "path2")) - # Test that first match takes precedence. - self.assertFalse(config.should_check("c", "path2/path1")) - - def test_path_with_different_case(self): - """Test a path that differs only in case.""" - base_rules = ["-"] - path_specific = [(["Foo/"], ["+whitespace"])] - user_rules = [] - - config = self._config(base_rules, path_specific, user_rules) - - self.assertFalse(config.should_check("whitespace", "Fooo/bar.txt")) - self.assertTrue(config.should_check("whitespace", "Foo/bar.txt")) - # Test different case. - self.assertTrue(config.should_check("whitespace", "FOO/bar.txt")) - - def test_user_rules(self): - """Test effect of user_rules on should_check().""" - base_rules = ["-"] - path_specific = [] - user_rules = ["+b"] - - config = self._config(base_rules, path_specific, user_rules) - - self.assertFalse(config.should_check("a", "path")) - self.assertTrue(config.should_check("b", "path")) - diff --git a/WebKitTools/Scripts/webkitpy/style/main.py b/WebKitTools/Scripts/webkitpy/style/main.py deleted file mode 100644 index c933c6d..0000000 --- a/WebKitTools/Scripts/webkitpy/style/main.py +++ /dev/null @@ -1,130 +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. - -import logging -import os -import sys - -from webkitpy.common.system.ospath import relpath as _relpath - - -_log = logging.getLogger(__name__) - - -def change_directory(checkout_root, paths, mock_os=None): - """Change the working directory to the WebKit checkout root, if possible. - - If every path in the paths parameter is below the checkout root (or if - the paths parameter is empty or None), this method changes the current - working directory to the checkout root and converts the paths parameter - as described below. - This allows the paths being checked to be displayed relative to the - checkout root, and for path-specific style checks to work as expected. - Path-specific checks include whether files should be skipped, whether - custom style rules should apply to certain files, etc. - If the checkout root is None or the empty string, this method returns - the paths parameter unchanged. - - Returns: - paths: A copy of the paths parameter -- possibly converted, as follows. - If this method changed the current working directory to the - checkout root, then the list is the paths parameter converted to - normalized paths relative to the checkout root. Otherwise, the - paths are not converted. - - Args: - paths: A list of paths to the files that should be checked for style. - This argument can be None or the empty list if a git commit - or all changes under the checkout root should be checked. - checkout_root: The path to the root of the WebKit checkout, or None or - the empty string if no checkout could be detected. - mock_os: A replacement module for unit testing. Defaults to os. - - """ - os_module = os if mock_os is None else mock_os - - if paths is not None: - paths = list(paths) - - if not checkout_root: - if not paths: - raise Exception("The paths parameter must be non-empty if " - "there is no checkout root.") - - # FIXME: Consider trying to detect the checkout root for each file - # being checked rather than only trying to detect the checkout - # root for the current working directory. This would allow - # files to be checked correctly even if the script is being - # run from outside any WebKit checkout. - # - # Moreover, try to find the "source root" for each file - # using path-based heuristics rather than using only the - # presence of a WebKit checkout. For example, we could - # examine parent directories until a directory is found - # containing JavaScriptCore, WebCore, WebKit, WebKitSite, - # and WebKitTools. - # Then log an INFO message saying that a source root not - # in a WebKit checkout was found. This will allow us to check - # the style of non-scm copies of the source tree (e.g. - # nightlies). - _log.warn("WebKit checkout root not found:\n" - " Path-dependent style checks may not work correctly.\n" - " See the help documentation for more info.") - - return paths - - if paths: - # Then try converting all of the paths to paths relative to - # the checkout root. - rel_paths = [] - for path in paths: - rel_path = _relpath(path, checkout_root) - if rel_path is None: - # Then the path is not below the checkout root. Since all - # paths should be interpreted relative to the same root, - # do not interpret any of the paths as relative to the - # checkout root. Interpret all of them relative to the - # current working directory, and do not change the current - # working directory. - _log.warn( -"""Path-dependent style checks may not work correctly: - - One of the given paths is outside the WebKit checkout of the current - working directory: - - Path: %s - Checkout root: %s - - Pass only files below the checkout root to ensure correct results. - See the help documentation for more info. -""" - % (path, checkout_root)) - - return paths - rel_paths.append(rel_path) - # If we got here, the conversion was successful. - paths = rel_paths - - _log.debug("Changing to checkout root: " + checkout_root) - os_module.chdir(checkout_root) - - return paths diff --git a/WebKitTools/Scripts/webkitpy/style/main_unittest.py b/WebKitTools/Scripts/webkitpy/style/main_unittest.py deleted file mode 100644 index fe448f5..0000000 --- a/WebKitTools/Scripts/webkitpy/style/main_unittest.py +++ /dev/null @@ -1,124 +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 main.py.""" - -import os -import unittest - -from main import change_directory -from webkitpy.style_references import LogTesting - - -class ChangeDirectoryTest(unittest.TestCase): - - """Tests change_directory().""" - - _original_directory = "/original" - _checkout_root = "/WebKit" - - class _MockOs(object): - - """A mock os module for unit testing.""" - - def __init__(self, test_case): - self._test_case = test_case - self._current_directory = \ - ChangeDirectoryTest._original_directory - - def chdir(self, current_directory): - self._current_directory = current_directory - - def assertCurrentDirectory(self, expected_directory): - self._test_case.assertEquals(expected_directory, - self._current_directory) - - def setUp(self): - self._log = LogTesting.setUp(self) - self._mock_os = self._MockOs(self) - - def tearDown(self): - self._log.tearDown() - - # This method is a convenient wrapper for change_working_directory() that - # passes the mock_os for this unit testing class. - def _change_directory(self, paths, checkout_root): - return change_directory(paths=paths, - checkout_root=checkout_root, - mock_os=self._mock_os) - - def _assert_result(self, actual_return_value, expected_return_value, - expected_log_messages, expected_current_directory): - self.assertEquals(actual_return_value, expected_return_value) - self._log.assertMessages(expected_log_messages) - self._mock_os.assertCurrentDirectory(expected_current_directory) - - def test_checkout_root_none_paths_none(self): - self.assertRaises(Exception, self._change_directory, - checkout_root=None, paths=None) - self._log.assertMessages([]) - self._mock_os.assertCurrentDirectory(self._original_directory) - - def test_checkout_root_none(self): - paths = self._change_directory(checkout_root=None, - paths=["path1"]) - log_messages = [ -"""WARNING: WebKit checkout root not found: - Path-dependent style checks may not work correctly. - See the help documentation for more info. -"""] - self._assert_result(paths, ["path1"], log_messages, - self._original_directory) - - def test_paths_none(self): - paths = self._change_directory(checkout_root=self._checkout_root, - paths=None) - self._assert_result(paths, None, [], self._checkout_root) - - def test_paths_convertible(self): - paths=["/WebKit/foo1.txt", - "/WebKit/foo2.txt"] - paths = self._change_directory(checkout_root=self._checkout_root, - paths=paths) - self._assert_result(paths, ["foo1.txt", "foo2.txt"], [], - self._checkout_root) - - def test_with_scm_paths_unconvertible(self): - paths=["/WebKit/foo1.txt", - "/outside/foo2.txt"] - paths = self._change_directory(checkout_root=self._checkout_root, - paths=paths) - log_messages = [ -"""WARNING: Path-dependent style checks may not work correctly: - - One of the given paths is outside the WebKit checkout of the current - working directory: - - Path: /outside/foo2.txt - Checkout root: /WebKit - - Pass only files below the checkout root to ensure correct results. - See the help documentation for more info. - -"""] - self._assert_result(paths, paths, log_messages, - self._original_directory) diff --git a/WebKitTools/Scripts/webkitpy/style/optparser.py b/WebKitTools/Scripts/webkitpy/style/optparser.py deleted file mode 100644 index f4e9923..0000000 --- a/WebKitTools/Scripts/webkitpy/style/optparser.py +++ /dev/null @@ -1,457 +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 parsing of command-line options for check-webkit-style.""" - -import logging -from optparse import OptionParser -import os.path -import sys - -from filter import validate_filter_rules -# This module should not import anything from checker.py. - -_log = logging.getLogger(__name__) - -_USAGE = """usage: %prog [--help] [options] [path1] [path2] ... - -Overview: - Check coding style according to WebKit style guidelines: - - http://webkit.org/coding/coding-style.html - - Path arguments can be files and directories. If neither a git commit nor - paths are passed, then all changes in your source control working directory - are checked. - -Style errors: - This script assigns to every style error a confidence score from 1-5 and - a category name. A confidence score of 5 means the error is certainly - a problem, and 1 means it could be fine. - - Category names appear in error messages in brackets, for example - [whitespace/indent]. See the options section below for an option that - displays all available categories and which are reported by default. - -Filters: - Use filters to configure what errors to report. Filters are specified using - a comma-separated list of boolean filter rules. The script reports errors - in a category if the category passes the filter, as described below. - - All categories start out passing. Boolean filter rules are then evaluated - from left to right, with later rules taking precedence. For example, the - rule "+foo" passes any category that starts with "foo", and "-foo" fails - any such category. The filter input "-whitespace,+whitespace/braces" fails - the category "whitespace/tab" and passes "whitespace/braces". - - Examples: --filter=-whitespace,+whitespace/braces - --filter=-whitespace,-runtime/printf,+runtime/printf_format - --filter=-,+build/include_what_you_use - -Paths: - Certain style-checking behavior depends on the paths relative to - the WebKit source root of the files being checked. For example, - certain types of errors may be handled differently for files in - WebKit/gtk/webkit/ (e.g. by suppressing "readability/naming" errors - for files in this directory). - - Consequently, if the path relative to the source root cannot be - determined for a file being checked, then style checking may not - work correctly for that file. This can occur, for example, if no - WebKit checkout can be found, or if the source root can be detected, - but one of the files being checked lies outside the source tree. - - If a WebKit checkout can be detected and all files being checked - are in the source tree, then all paths will automatically be - converted to paths relative to the source root prior to checking. - This is also useful for display purposes. - - Currently, this command can detect the source root only if the - command is run from within a WebKit checkout (i.e. if the current - working directory is below the root of a checkout). In particular, - it is not recommended to run this script from a directory outside - a checkout. - - Running this script from a top-level WebKit source directory and - checking only files in the source tree will ensure that all style - checking behaves correctly -- whether or not a checkout can be - detected. This is because all file paths will already be relative - to the source root and so will not need to be converted.""" - -_EPILOG = ("This script can miss errors and does not substitute for " - "code review.") - - -# This class should not have knowledge of the flag key names. -class DefaultCommandOptionValues(object): - - """Stores the default check-webkit-style command-line options. - - Attributes: - output_format: A string that is the default output format. - min_confidence: An integer that is the default minimum confidence level. - - """ - - def __init__(self, min_confidence, output_format): - self.min_confidence = min_confidence - self.output_format = output_format - - -# This class should not have knowledge of the flag key names. -class CommandOptionValues(object): - - """Stores the option values passed by the user via the command line. - - Attributes: - is_verbose: A boolean value of whether verbose logging is enabled. - - filter_rules: The list of filter rules provided by the user. - These rules are appended to the base rules and - path-specific rules and so take precedence over - the base filter rules, etc. - - git_commit: A string representing the git commit to check. - The default is None. - - min_confidence: An integer between 1 and 5 inclusive that is the - minimum confidence level of style errors to report. - The default is 1, which reports all errors. - - output_format: A string that is the output format. The supported - output formats are "emacs" which emacs can parse - and "vs7" which Microsoft Visual Studio 7 can parse. - - """ - def __init__(self, - filter_rules=None, - git_commit=None, - diff_files=None, - is_verbose=False, - min_confidence=1, - output_format="emacs"): - if filter_rules is None: - filter_rules = [] - - if (min_confidence < 1) or (min_confidence > 5): - raise ValueError('Invalid "min_confidence" parameter: value ' - "must be an integer between 1 and 5 inclusive. " - 'Value given: "%s".' % min_confidence) - - if output_format not in ("emacs", "vs7"): - raise ValueError('Invalid "output_format" parameter: ' - 'value must be "emacs" or "vs7". ' - 'Value given: "%s".' % output_format) - - self.filter_rules = filter_rules - self.git_commit = git_commit - self.diff_files = diff_files - self.is_verbose = is_verbose - self.min_confidence = min_confidence - self.output_format = output_format - - # Useful for unit testing. - def __eq__(self, other): - """Return whether this instance is equal to another.""" - if self.filter_rules != other.filter_rules: - return False - if self.git_commit != other.git_commit: - return False - if self.diff_files != other.diff_files: - return False - if self.is_verbose != other.is_verbose: - return False - if self.min_confidence != other.min_confidence: - return False - if self.output_format != other.output_format: - return False - - return True - - # Useful for unit testing. - def __ne__(self, other): - # Python does not automatically deduce this from __eq__(). - return not self.__eq__(other) - - -class ArgumentPrinter(object): - - """Supports the printing of check-webkit-style command arguments.""" - - def _flag_pair_to_string(self, flag_key, flag_value): - return '--%(key)s=%(val)s' % {'key': flag_key, 'val': flag_value } - - def to_flag_string(self, options): - """Return a flag string of the given CommandOptionValues instance. - - This method orders the flag values alphabetically by the flag key. - - Args: - options: A CommandOptionValues instance. - - """ - flags = {} - flags['min-confidence'] = options.min_confidence - flags['output'] = options.output_format - # Only include the filter flag if user-provided rules are present. - filter_rules = options.filter_rules - if filter_rules: - flags['filter'] = ",".join(filter_rules) - if options.git_commit: - flags['git-commit'] = options.git_commit - if options.diff_files: - flags['diff_files'] = options.diff_files - - flag_string = '' - # Alphabetizing lets us unit test this method. - for key in sorted(flags.keys()): - flag_string += self._flag_pair_to_string(key, flags[key]) + ' ' - - return flag_string.strip() - - -class ArgumentParser(object): - - # FIXME: Move the documentation of the attributes to the __init__ - # docstring after making the attributes internal. - """Supports the parsing of check-webkit-style command arguments. - - Attributes: - create_usage: A function that accepts a DefaultCommandOptionValues - instance and returns a string of usage instructions. - Defaults to the function that generates the usage - string for check-webkit-style. - default_options: A DefaultCommandOptionValues instance that provides - the default values for options not explicitly - provided by the user. - stderr_write: A function that takes a string as a parameter and - serves as stderr.write. Defaults to sys.stderr.write. - This parameter should be specified only for unit tests. - - """ - - def __init__(self, - all_categories, - default_options, - base_filter_rules=None, - mock_stderr=None, - usage=None): - """Create an ArgumentParser instance. - - Args: - all_categories: The set of all available style categories. - default_options: See the corresponding attribute in the class - docstring. - Keyword Args: - base_filter_rules: The list of filter rules at the beginning of - the list of rules used to check style. This - list has the least precedence when checking - style and precedes any user-provided rules. - The class uses this parameter only for display - purposes to the user. Defaults to the empty list. - create_usage: See the documentation of the corresponding - attribute in the class docstring. - stderr_write: See the documentation of the corresponding - attribute in the class docstring. - - """ - if base_filter_rules is None: - base_filter_rules = [] - stderr = sys.stderr if mock_stderr is None else mock_stderr - if usage is None: - usage = _USAGE - - self._all_categories = all_categories - self._base_filter_rules = base_filter_rules - - # FIXME: Rename these to reflect that they are internal. - self.default_options = default_options - self.stderr_write = stderr.write - - self._parser = self._create_option_parser(stderr=stderr, - usage=usage, - default_min_confidence=self.default_options.min_confidence, - default_output_format=self.default_options.output_format) - - def _create_option_parser(self, stderr, usage, - default_min_confidence, default_output_format): - # Since the epilog string is short, it is not necessary to replace - # the epilog string with a mock epilog string when testing. - # For this reason, we use _EPILOG directly rather than passing it - # as an argument like we do for the usage string. - parser = OptionParser(usage=usage, epilog=_EPILOG) - - filter_help = ('set a filter to control what categories of style ' - 'errors to report. Specify a filter using a comma-' - 'delimited list of boolean filter rules, for example ' - '"--filter -whitespace,+whitespace/braces". To display ' - 'all categories and which are enabled by default, pass ' - """no value (e.g. '-f ""' or '--filter=').""") - parser.add_option("-f", "--filter-rules", metavar="RULES", - dest="filter_value", help=filter_help) - - git_commit_help = ("check all changes in the given commit. " - "Use 'commit_id..' to check all changes after commmit_id") - parser.add_option("-g", "--git-diff", "--git-commit", - metavar="COMMIT", dest="git_commit", help=git_commit_help,) - - diff_files_help = "diff the files passed on the command line rather than checking the style of every line" - parser.add_option("--diff-files", action="store_true", dest="diff_files", default=False, help=diff_files_help) - - min_confidence_help = ("set the minimum confidence of style errors " - "to report. Can be an integer 1-5, with 1 " - "displaying all errors. Defaults to %default.") - parser.add_option("-m", "--min-confidence", metavar="INT", - type="int", dest="min_confidence", - default=default_min_confidence, - help=min_confidence_help) - - output_format_help = ('set the output format, which can be "emacs" ' - 'or "vs7" (for Visual Studio). ' - 'Defaults to "%default".') - parser.add_option("-o", "--output-format", metavar="FORMAT", - choices=["emacs", "vs7"], - dest="output_format", default=default_output_format, - help=output_format_help) - - verbose_help = "enable verbose logging." - parser.add_option("-v", "--verbose", dest="is_verbose", default=False, - action="store_true", help=verbose_help) - - # Override OptionParser's error() method so that option help will - # also display when an error occurs. Normally, just the usage - # string displays and not option help. - parser.error = self._parse_error - - # Override OptionParser's print_help() method so that help output - # does not render to the screen while running unit tests. - print_help = parser.print_help - parser.print_help = lambda: print_help(file=stderr) - - return parser - - def _parse_error(self, error_message): - """Print the help string and an error message, and exit.""" - # The method format_help() includes both the usage string and - # the flag options. - help = self._parser.format_help() - # Separate help from the error message with a single blank line. - self.stderr_write(help + "\n") - if error_message: - _log.error(error_message) - - # Since we are using this method to replace/override the Python - # module optparse's OptionParser.error() method, we match its - # behavior and exit with status code 2. - # - # As additional background, Python documentation says-- - # - # "Unix programs generally use 2 for command line syntax errors - # and 1 for all other kind of errors." - # - # (from http://docs.python.org/library/sys.html#sys.exit ) - sys.exit(2) - - def _exit_with_categories(self): - """Exit and print the style categories and default filter rules.""" - self.stderr_write('\nAll categories:\n') - for category in sorted(self._all_categories): - self.stderr_write(' ' + category + '\n') - - self.stderr_write('\nDefault filter rules**:\n') - for filter_rule in sorted(self._base_filter_rules): - self.stderr_write(' ' + filter_rule + '\n') - self.stderr_write('\n**The command always evaluates the above rules, ' - 'and before any --filter flag.\n\n') - - sys.exit(0) - - def _parse_filter_flag(self, flag_value): - """Parse the --filter flag, and return a list of filter rules. - - Args: - flag_value: A string of comma-separated filter rules, for - example "-whitespace,+whitespace/indent". - - """ - filters = [] - for uncleaned_filter in flag_value.split(','): - filter = uncleaned_filter.strip() - if not filter: - continue - filters.append(filter) - return filters - - def parse(self, args): - """Parse the command line arguments to check-webkit-style. - - Args: - args: A list of command-line arguments as returned by sys.argv[1:]. - - Returns: - A tuple of (paths, options) - - paths: The list of paths to check. - options: A CommandOptionValues instance. - - """ - (options, paths) = self._parser.parse_args(args=args) - - filter_value = options.filter_value - git_commit = options.git_commit - diff_files = options.diff_files - is_verbose = options.is_verbose - min_confidence = options.min_confidence - output_format = options.output_format - - if filter_value is not None and not filter_value: - # Then the user explicitly passed no filter, for - # example "-f ''" or "--filter=". - self._exit_with_categories() - - # Validate user-provided values. - - min_confidence = int(min_confidence) - if (min_confidence < 1) or (min_confidence > 5): - self._parse_error('option --min-confidence: invalid integer: ' - '%s: value must be between 1 and 5' - % min_confidence) - - if filter_value: - filter_rules = self._parse_filter_flag(filter_value) - else: - filter_rules = [] - - try: - validate_filter_rules(filter_rules, self._all_categories) - except ValueError, err: - self._parse_error(err) - - options = CommandOptionValues(filter_rules=filter_rules, - git_commit=git_commit, - diff_files=diff_files, - is_verbose=is_verbose, - min_confidence=min_confidence, - output_format=output_format) - - return (paths, options) - diff --git a/WebKitTools/Scripts/webkitpy/style/optparser_unittest.py b/WebKitTools/Scripts/webkitpy/style/optparser_unittest.py deleted file mode 100644 index a6b64da..0000000 --- a/WebKitTools/Scripts/webkitpy/style/optparser_unittest.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. - -"""Unit tests for parser.py.""" - -import unittest - -from webkitpy.common.system.logtesting import LoggingTestCase -from webkitpy.style.optparser import ArgumentParser -from webkitpy.style.optparser import ArgumentPrinter -from webkitpy.style.optparser import CommandOptionValues as ProcessorOptions -from webkitpy.style.optparser import DefaultCommandOptionValues - - -class ArgumentPrinterTest(unittest.TestCase): - - """Tests the ArgumentPrinter class.""" - - _printer = ArgumentPrinter() - - def _create_options(self, - output_format='emacs', - min_confidence=3, - filter_rules=[], - git_commit=None): - return ProcessorOptions(filter_rules=filter_rules, - git_commit=git_commit, - min_confidence=min_confidence, - output_format=output_format) - - def test_to_flag_string(self): - options = self._create_options('vs7', 5, ['+foo', '-bar'], 'git') - self.assertEquals('--filter=+foo,-bar --git-commit=git ' - '--min-confidence=5 --output=vs7', - self._printer.to_flag_string(options)) - - # This is to check that --filter and --git-commit do not - # show up when not user-specified. - options = self._create_options() - self.assertEquals('--min-confidence=3 --output=emacs', - self._printer.to_flag_string(options)) - - -class ArgumentParserTest(LoggingTestCase): - - """Test the ArgumentParser class.""" - - class _MockStdErr(object): - - def write(self, message): - # We do not want the usage string or style categories - # to print during unit tests, so print nothing. - return - - def _parse(self, args): - """Call a test parser.parse().""" - parser = self._create_parser() - return parser.parse(args) - - def _create_defaults(self): - """Return a DefaultCommandOptionValues instance for testing.""" - base_filter_rules = ["-", "+whitespace"] - return DefaultCommandOptionValues(min_confidence=3, - output_format="vs7") - - def _create_parser(self): - """Return an ArgumentParser instance for testing.""" - default_options = self._create_defaults() - - all_categories = ["build" ,"whitespace"] - - mock_stderr = self._MockStdErr() - - return ArgumentParser(all_categories=all_categories, - base_filter_rules=[], - default_options=default_options, - mock_stderr=mock_stderr, - usage="test usage") - - def test_parse_documentation(self): - parse = self._parse - - # FIXME: Test both the printing of the usage string and the - # filter categories help. - - # Request the usage string. - self.assertRaises(SystemExit, parse, ['--help']) - # Request default filter rules and available style categories. - self.assertRaises(SystemExit, parse, ['--filter=']) - - def test_parse_bad_values(self): - parse = self._parse - - # Pass an unsupported argument. - self.assertRaises(SystemExit, parse, ['--bad']) - self.assertLog(['ERROR: no such option: --bad\n']) - - self.assertRaises(SystemExit, parse, ['--min-confidence=bad']) - self.assertLog(['ERROR: option --min-confidence: ' - "invalid integer value: 'bad'\n"]) - self.assertRaises(SystemExit, parse, ['--min-confidence=0']) - self.assertLog(['ERROR: option --min-confidence: invalid integer: 0: ' - 'value must be between 1 and 5\n']) - self.assertRaises(SystemExit, parse, ['--min-confidence=6']) - self.assertLog(['ERROR: option --min-confidence: invalid integer: 6: ' - 'value must be between 1 and 5\n']) - parse(['--min-confidence=1']) # works - parse(['--min-confidence=5']) # works - - self.assertRaises(SystemExit, parse, ['--output=bad']) - self.assertLog(['ERROR: option --output-format: invalid choice: ' - "'bad' (choose from 'emacs', 'vs7')\n"]) - parse(['--output=vs7']) # works - - # Pass a filter rule not beginning with + or -. - self.assertRaises(SystemExit, parse, ['--filter=build']) - self.assertLog(['ERROR: Invalid filter rule "build": ' - 'every rule must start with + or -.\n']) - parse(['--filter=+build']) # works - - def test_parse_default_arguments(self): - parse = self._parse - - (files, options) = parse([]) - - self.assertEquals(files, []) - - self.assertEquals(options.filter_rules, []) - self.assertEquals(options.git_commit, None) - self.assertEquals(options.diff_files, False) - self.assertEquals(options.is_verbose, False) - self.assertEquals(options.min_confidence, 3) - self.assertEquals(options.output_format, 'vs7') - - def test_parse_explicit_arguments(self): - parse = self._parse - - # Pass non-default explicit values. - (files, options) = parse(['--min-confidence=4']) - self.assertEquals(options.min_confidence, 4) - (files, options) = parse(['--output=emacs']) - self.assertEquals(options.output_format, 'emacs') - (files, options) = parse(['-g', 'commit']) - self.assertEquals(options.git_commit, 'commit') - (files, options) = parse(['--git-commit=commit']) - self.assertEquals(options.git_commit, 'commit') - (files, options) = parse(['--git-diff=commit']) - self.assertEquals(options.git_commit, 'commit') - (files, options) = parse(['--verbose']) - self.assertEquals(options.is_verbose, True) - (files, options) = parse(['--diff-files', 'file.txt']) - self.assertEquals(options.diff_files, True) - - # Pass user_rules. - (files, options) = parse(['--filter=+build,-whitespace']) - self.assertEquals(options.filter_rules, - ["+build", "-whitespace"]) - - # Pass spurious white space in user rules. - (files, options) = parse(['--filter=+build, -whitespace']) - self.assertEquals(options.filter_rules, - ["+build", "-whitespace"]) - - def test_parse_files(self): - parse = self._parse - - (files, options) = parse(['foo.cpp']) - self.assertEquals(files, ['foo.cpp']) - - # Pass multiple files. - (files, options) = parse(['--output=emacs', 'foo.cpp', 'bar.cpp']) - self.assertEquals(files, ['foo.cpp', 'bar.cpp']) - - -class CommandOptionValuesTest(unittest.TestCase): - - """Tests CommandOptionValues class.""" - - def test_init(self): - """Test __init__ constructor.""" - # Check default parameters. - options = ProcessorOptions() - self.assertEquals(options.filter_rules, []) - self.assertEquals(options.git_commit, None) - self.assertEquals(options.is_verbose, False) - self.assertEquals(options.min_confidence, 1) - self.assertEquals(options.output_format, "emacs") - - # Check argument validation. - self.assertRaises(ValueError, ProcessorOptions, output_format="bad") - ProcessorOptions(output_format="emacs") # No ValueError: works - ProcessorOptions(output_format="vs7") # works - self.assertRaises(ValueError, ProcessorOptions, min_confidence=0) - self.assertRaises(ValueError, ProcessorOptions, min_confidence=6) - ProcessorOptions(min_confidence=1) # works - ProcessorOptions(min_confidence=5) # works - - # Check attributes. - options = ProcessorOptions(filter_rules=["+"], - git_commit="commit", - is_verbose=True, - min_confidence=3, - output_format="vs7") - self.assertEquals(options.filter_rules, ["+"]) - self.assertEquals(options.git_commit, "commit") - self.assertEquals(options.is_verbose, True) - self.assertEquals(options.min_confidence, 3) - self.assertEquals(options.output_format, "vs7") - - def test_eq(self): - """Test __eq__ equality function.""" - self.assertTrue(ProcessorOptions().__eq__(ProcessorOptions())) - - # Also verify that a difference in any argument causes equality to fail. - - # Explicitly create a ProcessorOptions instance with all default - # values. We do this to be sure we are assuming the right default - # values in our self.assertFalse() calls below. - options = ProcessorOptions(filter_rules=[], - git_commit=None, - is_verbose=False, - min_confidence=1, - output_format="emacs") - # Verify that we created options correctly. - self.assertTrue(options.__eq__(ProcessorOptions())) - - self.assertFalse(options.__eq__(ProcessorOptions(filter_rules=["+"]))) - self.assertFalse(options.__eq__(ProcessorOptions(git_commit="commit"))) - self.assertFalse(options.__eq__(ProcessorOptions(is_verbose=True))) - self.assertFalse(options.__eq__(ProcessorOptions(min_confidence=2))) - self.assertFalse(options.__eq__(ProcessorOptions(output_format="vs7"))) - - def test_ne(self): - """Test __ne__ inequality function.""" - # By default, __ne__ always returns true on different objects. - # Thus, just check the distinguishing case to verify that the - # code defines __ne__. - self.assertFalse(ProcessorOptions().__ne__(ProcessorOptions())) - diff --git a/WebKitTools/Scripts/webkitpy/style/patchreader.py b/WebKitTools/Scripts/webkitpy/style/patchreader.py deleted file mode 100644 index f44839d..0000000 --- a/WebKitTools/Scripts/webkitpy/style/patchreader.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) -# Copyright (C) 2010 ProFUSION embedded systems -# -# 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 - -from webkitpy.common.checkout.diff_parser import DiffParser - - -_log = logging.getLogger("webkitpy.style.patchreader") - - -class PatchReader(object): - """Supports checking style in patches.""" - - def __init__(self, text_file_reader): - """Create a PatchReader instance. - - Args: - text_file_reader: A TextFileReader instance. - - """ - self._text_file_reader = text_file_reader - - def check(self, patch_string): - """Check style in the given patch.""" - patch_files = DiffParser(patch_string.splitlines()).files - - for path, diff_file in patch_files.iteritems(): - line_numbers = diff_file.added_or_modified_line_numbers() - _log.debug('Found %s new or modified lines in: %s' % (len(line_numbers), path)) - - if not line_numbers: - # Don't check files which contain only deleted lines - # as they can never add style errors. However, mark them as - # processed so that we count up number of such files. - self._text_file_reader.count_delete_only_file() - continue - - self._text_file_reader.process_file(file_path=path, line_numbers=line_numbers) diff --git a/WebKitTools/Scripts/webkitpy/style/patchreader_unittest.py b/WebKitTools/Scripts/webkitpy/style/patchreader_unittest.py deleted file mode 100644 index b121082..0000000 --- a/WebKitTools/Scripts/webkitpy/style/patchreader_unittest.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2010 Google Inc. All rights reserved. -# Copyright (C) 2009 Torch Mobile Inc. -# Copyright (C) 2009 Apple Inc. All rights reserved. -# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) -# -# 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.style.patchreader import PatchReader - - -class PatchReaderTest(unittest.TestCase): - - """Test the PatchReader class.""" - - class MockTextFileReader(object): - - def __init__(self): - self.passed_to_process_file = [] - """A list of (file_path, line_numbers) pairs.""" - self.delete_only_file_count = 0 - """A number of times count_delete_only_file() called""" - - def process_file(self, file_path, line_numbers): - self.passed_to_process_file.append((file_path, line_numbers)) - - def count_delete_only_file(self): - self.delete_only_file_count += 1 - - def setUp(self): - file_reader = self.MockTextFileReader() - self._file_reader = file_reader - self._patch_checker = PatchReader(file_reader) - - def _call_check_patch(self, patch_string): - self._patch_checker.check(patch_string) - - def _assert_checked(self, passed_to_process_file, delete_only_file_count): - self.assertEquals(self._file_reader.passed_to_process_file, - passed_to_process_file) - self.assertEquals(self._file_reader.delete_only_file_count, - delete_only_file_count) - - def test_check_patch(self): - # The modified line_numbers array for this patch is: [2]. - self._call_check_patch("""diff --git a/__init__.py b/__init__.py -index ef65bee..e3db70e 100644 ---- a/__init__.py -+++ b/__init__.py -@@ -1,1 +1,2 @@ - # Required for Python to search this directory for module files -+# New line -""") - self._assert_checked([("__init__.py", [2])], 0) - - def test_check_patch_with_deletion(self): - self._call_check_patch("""Index: __init__.py -=================================================================== ---- __init__.py (revision 3593) -+++ __init__.py (working copy) -@@ -1 +0,0 @@ --foobar -""") - # _mock_check_file should not be called for the deletion patch. - self._assert_checked([], 1) diff --git a/WebKitTools/Scripts/webkitpy/style_references.py b/WebKitTools/Scripts/webkitpy/style_references.py deleted file mode 100644 index a21e931..0000000 --- a/WebKitTools/Scripts/webkitpy/style_references.py +++ /dev/null @@ -1,74 +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. - -"""References to non-style modules used by the style package.""" - -# This module is a simple facade to the functionality used by the -# style package that comes from WebKit modules outside the style -# package. -# -# With this module, the only intra-package references (i.e. -# references to webkitpy modules outside the style folder) that -# the style package needs to make are relative references to -# this module. For example-- -# -# > from .. style_references import parse_patch -# -# Similarly, people maintaining non-style code are not beholden -# to the contents of the style package when refactoring or -# otherwise changing non-style code. They only have to be aware -# of this module. - -import os - -from webkitpy.common.checkout.diff_parser import DiffParser -from webkitpy.common.system.logtesting import LogTesting -from webkitpy.common.system.logtesting import TestLogStream -from webkitpy.common.system.logutils import configure_logging -from webkitpy.common.checkout.scm import detect_scm_system -from webkitpy.layout_tests import port -from webkitpy.layout_tests.layout_package import test_expectations -from webkitpy.thirdparty.autoinstalled import pep8 - - -def detect_checkout(): - """Return a WebKitCheckout instance, or None if it cannot be found.""" - cwd = os.path.abspath(os.curdir) - scm = detect_scm_system(cwd) - - return None if scm is None else WebKitCheckout(scm) - - -class WebKitCheckout(object): - - """Simple facade to the SCM class for use by style package.""" - - def __init__(self, scm): - self._scm = scm - - def root_path(self): - """Return the checkout root as an absolute path.""" - return self._scm.checkout_root - - def create_patch(self, git_commit, changed_files=None): - # FIXME: SCM.create_patch should understand how to handle None. - return self._scm.create_patch(git_commit, changed_files=changed_files or []) diff --git a/WebKitTools/Scripts/webkitpy/test/__init__.py b/WebKitTools/Scripts/webkitpy/test/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/test/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/test/cat.py b/WebKitTools/Scripts/webkitpy/test/cat.py deleted file mode 100644 index ae1e143..0000000 --- a/WebKitTools/Scripts/webkitpy/test/cat.py +++ /dev/null @@ -1,42 +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 os.path -import sys - -# Add WebKitTools/Scripts to the path to ensure we can find webkitpy. -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - -from webkitpy.common.system import fileutils - - -def command_arguments(*args): - return ['python', __file__] + list(args) - - -def main(): - fileutils.make_stdout_binary() - sys.stdout.write(sys.stdin.read()) - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/WebKitTools/Scripts/webkitpy/test/cat_unittest.py b/WebKitTools/Scripts/webkitpy/test/cat_unittest.py deleted file mode 100644 index 4ed1f67..0000000 --- a/WebKitTools/Scripts/webkitpy/test/cat_unittest.py +++ /dev/null @@ -1,52 +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 StringIO -import os.path -import sys -import unittest - -from webkitpy.common.system import executive, outputcapture -from webkitpy.test import cat - - -class CatTest(outputcapture.OutputCaptureTestCaseBase): - def assert_cat(self, input): - saved_stdin = sys.stdin - sys.stdin = StringIO.StringIO(input) - cat.main() - self.assertStdout(input) - sys.stdin = saved_stdin - - def test_basic(self): - self.assert_cat('foo bar baz\n') - - def test_no_newline(self): - self.assert_cat('foo bar baz') - - def test_unicode(self): - self.assert_cat(u'WebKit \u2661 Tor Arne Vestb\u00F8!') - - def test_as_command(self): - input = 'foo bar baz\n' - output = executive.Executive().run_command(cat.command_arguments(), input=input) - self.assertEqual(input, output) diff --git a/WebKitTools/Scripts/webkitpy/test/echo.py b/WebKitTools/Scripts/webkitpy/test/echo.py deleted file mode 100644 index f7468f7..0000000 --- a/WebKitTools/Scripts/webkitpy/test/echo.py +++ /dev/null @@ -1,52 +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 os.path -import sys - -# Add WebKitTools/Scripts to the path to ensure we can find webkitpy. -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - -from webkitpy.common.system import fileutils - - -def command_arguments(*args): - return ['python', __file__] + list(args) - - -def main(args=None): - if args is None: - args = sys.argv[1:] - - fileutils.make_stdout_binary() - - print_newline = True - if len(args) and args[0] == '-n': - print_newline = False - del args[0] - sys.stdout.write(' '.join(args)) - if print_newline: - sys.stdout.write('\n') - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/WebKitTools/Scripts/webkitpy/test/echo_unittest.py b/WebKitTools/Scripts/webkitpy/test/echo_unittest.py deleted file mode 100644 index bc13b5e..0000000 --- a/WebKitTools/Scripts/webkitpy/test/echo_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 os.path -import sys -import unittest - -from webkitpy.common.system import executive, outputcapture -from webkitpy.test import echo - - -class EchoTest(outputcapture.OutputCaptureTestCaseBase): - def test_basic(self): - echo.main(['foo', 'bar', 'baz']) - self.assertStdout('foo bar baz\n') - - def test_no_newline(self): - echo.main(['-n', 'foo', 'bar', 'baz']) - self.assertStdout('foo bar baz') - - def test_unicode(self): - echo.main([u'WebKit \u2661', 'Tor Arne', u'Vestb\u00F8!']) - self.assertStdout(u'WebKit \u2661 Tor Arne Vestb\u00F8!\n') - - def test_argument_order(self): - echo.main(['foo', '-n', 'bar']) - self.assertStdout('foo -n bar\n') - - def test_empty_arguments(self): - old_argv = sys.argv - sys.argv = ['echo.py', 'foo', 'bar', 'baz'] - echo.main([]) - self.assertStdout('\n') - sys.argv = old_argv - - def test_no_arguments(self): - old_argv = sys.argv - sys.argv = ['echo.py', 'foo', 'bar', 'baz'] - echo.main() - self.assertStdout('foo bar baz\n') - sys.argv = old_argv - - def test_as_command(self): - output = executive.Executive().run_command(echo.command_arguments('foo', 'bar', 'baz')) - self.assertEqual(output, 'foo bar baz\n') diff --git a/WebKitTools/Scripts/webkitpy/test/main.py b/WebKitTools/Scripts/webkitpy/test/main.py deleted file mode 100644 index 1038d82..0000000 --- a/WebKitTools/Scripts/webkitpy/test/main.py +++ /dev/null @@ -1,140 +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 the entry method for test-webkitpy.""" - -import logging -import os -import sys -import unittest - -import webkitpy - - -_log = logging.getLogger(__name__) - - -class Tester(object): - - """Discovers and runs webkitpy unit tests.""" - - def _find_unittest_files(self, webkitpy_dir): - """Return a list of paths to all unit-test files.""" - unittest_paths = [] # Return value. - - for dir_path, dir_names, file_names in os.walk(webkitpy_dir): - for file_name in file_names: - if not file_name.endswith("_unittest.py"): - continue - unittest_path = os.path.join(dir_path, file_name) - unittest_paths.append(unittest_path) - - return unittest_paths - - def _modules_from_paths(self, package_root, paths): - """Return a list of fully-qualified module names given paths.""" - package_path = os.path.abspath(package_root) - root_package_name = os.path.split(package_path)[1] # Equals "webkitpy". - - prefix_length = len(package_path) - - modules = [] - for path in paths: - path = os.path.abspath(path) - # This gives us, for example: /common/config/ports_unittest.py - rel_path = path[prefix_length:] - # This gives us, for example: /common/config/ports_unittest - rel_path = os.path.splitext(rel_path)[0] - - parts = [] - while True: - (rel_path, tail) = os.path.split(rel_path) - if not tail: - break - parts.insert(0, tail) - # We now have, for example: common.config.ports_unittest - # FIXME: This is all a hack around the fact that we always prefix webkitpy includes with "webkitpy." - parts.insert(0, root_package_name) # Put "webkitpy" at the beginning. - module = ".".join(parts) - modules.append(module) - - return modules - - def run_tests(self, sys_argv, external_package_paths=None): - """Run the unit tests in all *_unittest.py modules in webkitpy. - - This method excludes "webkitpy.common.checkout.scm_unittest" unless - the --all option is the second element of sys_argv. - - Args: - sys_argv: A reference to sys.argv. - - """ - if external_package_paths is None: - external_package_paths = [] - else: - # FIXME: We should consider moving webkitpy off of using "webkitpy." to prefix - # all includes. If we did that, then this would use path instead of dirname(path). - # QueueStatusServer.__init__ has a sys.path import hack due to this code. - sys.path.extend(set(os.path.dirname(path) for path in external_package_paths)) - - if len(sys_argv) > 1 and not sys_argv[-1].startswith("-"): - # Then explicit modules or test names were provided, which - # the unittest module is equipped to handle. - unittest.main(argv=sys_argv, module=None) - # No need to return since unitttest.main() exits. - - # Otherwise, auto-detect all unit tests. - - # FIXME: This should be combined with the external_package_paths code above. - webkitpy_dir = os.path.dirname(webkitpy.__file__) - - modules = [] - for path in [webkitpy_dir] + external_package_paths: - modules.extend(self._modules_from_paths(path, self._find_unittest_files(path))) - modules.sort() - - # This is a sanity check to ensure that the unit-test discovery - # methods are working. - if len(modules) < 1: - raise Exception("No unit-test modules found.") - - for module in modules: - _log.debug("Found: %s" % module) - - # FIXME: This is a hack, but I'm tired of commenting out the test. - # See https://bugs.webkit.org/show_bug.cgi?id=31818 - if len(sys_argv) > 1 and sys.argv[1] == "--all": - sys.argv.remove("--all") - else: - excluded_module = "webkitpy.common.checkout.scm_unittest" - _log.info("Excluding: %s (use --all to include)" % excluded_module) - modules.remove(excluded_module) - - sys_argv.extend(modules) - - # We pass None for the module because we do not want the unittest - # module to resolve module names relative to a given module. - # (This would require importing all of the unittest modules from - # this module.) See the loadTestsFromName() method of the - # unittest.TestLoader class for more details on this parameter. - unittest.main(argv=sys_argv, module=None) diff --git a/WebKitTools/Scripts/webkitpy/test/skip.py b/WebKitTools/Scripts/webkitpy/test/skip.py deleted file mode 100644 index 8587d56..0000000 --- a/WebKitTools/Scripts/webkitpy/test/skip.py +++ /dev/null @@ -1,52 +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 logging - -_log = logging.getLogger(__name__) - - -def skip_if(klass, condition, message=None, logger=None): - """Makes all test_* methods in a given class no-ops if the given condition - is False. Backported from Python 3.1+'s unittest.skipIf decorator.""" - if not logger: - logger = _log - if not condition: - return klass - for name in dir(klass): - attr = getattr(klass, name) - if not callable(attr): - continue - if not name.startswith('test_'): - continue - setattr(klass, name, _skipped_method(attr, message, logger)) - klass._printed_skipped_message = False - return klass - - -def _skipped_method(method, message, logger): - def _skip(*args): - if method.im_class._printed_skipped_message: - return - method.im_class._printed_skipped_message = True - logger.info('Skipping %s.%s: %s' % (method.__module__, method.im_class.__name__, message)) - return _skip diff --git a/WebKitTools/Scripts/webkitpy/test/skip_unittest.py b/WebKitTools/Scripts/webkitpy/test/skip_unittest.py deleted file mode 100644 index f61a1bb..0000000 --- a/WebKitTools/Scripts/webkitpy/test/skip_unittest.py +++ /dev/null @@ -1,77 +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 StringIO -import logging -import unittest - -from webkitpy.test.skip import skip_if - - -class SkipTest(unittest.TestCase): - def setUp(self): - self.logger = logging.getLogger(__name__) - - self.old_level = self.logger.level - self.logger.setLevel(logging.INFO) - - self.old_propagate = self.logger.propagate - self.logger.propagate = False - - self.log_stream = StringIO.StringIO() - self.handler = logging.StreamHandler(self.log_stream) - self.logger.addHandler(self.handler) - - self.foo_was_called = False - - def tearDown(self): - self.logger.removeHandler(self.handler) - self.propagate = self.old_propagate - self.logger.setLevel(self.old_level) - - def create_fixture_class(self): - class TestSkipFixture(object): - def __init__(self, callback): - self.callback = callback - - def test_foo(self): - self.callback() - - return TestSkipFixture - - def foo_callback(self): - self.foo_was_called = True - - def test_skip_if_false(self): - klass = skip_if(self.create_fixture_class(), False, 'Should not see this message.', logger=self.logger) - klass(self.foo_callback).test_foo() - self.assertEqual(self.log_stream.getvalue(), '') - self.assertTrue(self.foo_was_called) - - def test_skip_if_true(self): - klass = skip_if(self.create_fixture_class(), True, 'Should see this message.', logger=self.logger) - klass(self.foo_callback).test_foo() - self.assertEqual(self.log_stream.getvalue(), 'Skipping webkitpy.test.skip_unittest.TestSkipFixture: Should see this message.\n') - self.assertFalse(self.foo_was_called) - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/BeautifulSoup.py b/WebKitTools/Scripts/webkitpy/thirdparty/BeautifulSoup.py deleted file mode 100644 index 34204e7..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/BeautifulSoup.py +++ /dev/null @@ -1,2000 +0,0 @@ -"""Beautiful Soup -Elixir and Tonic -"The Screen-Scraper's Friend" -http://www.crummy.com/software/BeautifulSoup/ - -Beautiful Soup parses a (possibly invalid) XML or HTML document into a -tree representation. It provides methods and Pythonic idioms that make -it easy to navigate, search, and modify the tree. - -A well-formed XML/HTML document yields a well-formed data -structure. An ill-formed XML/HTML document yields a correspondingly -ill-formed data structure. If your document is only locally -well-formed, you can use this library to find and process the -well-formed part of it. - -Beautiful Soup works with Python 2.2 and up. It has no external -dependencies, but you'll have more success at converting data to UTF-8 -if you also install these three packages: - -* chardet, for auto-detecting character encodings - http://chardet.feedparser.org/ -* cjkcodecs and iconv_codec, which add more encodings to the ones supported - by stock Python. - http://cjkpython.i18n.org/ - -Beautiful Soup defines classes for two main parsing strategies: - - * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific - language that kind of looks like XML. - - * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid - or invalid. This class has web browser-like heuristics for - obtaining a sensible parse tree in the face of common HTML errors. - -Beautiful Soup also defines a class (UnicodeDammit) for autodetecting -the encoding of an HTML or XML document, and converting it to -Unicode. Much of this code is taken from Mark Pilgrim's Universal Feed Parser. - -For more than you ever wanted to know about Beautiful Soup, see the -documentation: -http://www.crummy.com/software/BeautifulSoup/documentation.html - -Here, have some legalese: - -Copyright (c) 2004-2009, Leonard Richardson - -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 the the Beautiful Soup Consortium and All - Night Kosher Bakery 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, DAMMIT. - -""" -from __future__ import generators - -__author__ = "Leonard Richardson (leonardr@segfault.org)" -__version__ = "3.1.0.1" -__copyright__ = "Copyright (c) 2004-2009 Leonard Richardson" -__license__ = "New-style BSD" - -import codecs -import markupbase -import types -import re -from HTMLParser import HTMLParser, HTMLParseError -try: - from htmlentitydefs import name2codepoint -except ImportError: - name2codepoint = {} -try: - set -except NameError: - from sets import Set as set - -#These hacks make Beautiful Soup able to parse XML with namespaces -markupbase._declname_match = re.compile(r'[a-zA-Z][-_.:a-zA-Z0-9]*\s*').match - -DEFAULT_OUTPUT_ENCODING = "utf-8" - -# First, the classes that represent markup elements. - -def sob(unicode, encoding): - """Returns either the given Unicode string or its encoding.""" - if encoding is None: - return unicode - else: - return unicode.encode(encoding) - -class PageElement: - """Contains the navigational information for some part of the page - (either a tag or a piece of text)""" - - def setup(self, parent=None, previous=None): - """Sets up the initial relations between this element and - other elements.""" - self.parent = parent - self.previous = previous - self.next = None - self.previousSibling = None - self.nextSibling = None - if self.parent and self.parent.contents: - self.previousSibling = self.parent.contents[-1] - self.previousSibling.nextSibling = self - - def replaceWith(self, replaceWith): - oldParent = self.parent - myIndex = self.parent.contents.index(self) - if hasattr(replaceWith, 'parent') and replaceWith.parent == self.parent: - # We're replacing this element with one of its siblings. - index = self.parent.contents.index(replaceWith) - if index and index < myIndex: - # Furthermore, it comes before this element. That - # means that when we extract it, the index of this - # element will change. - myIndex = myIndex - 1 - self.extract() - oldParent.insert(myIndex, replaceWith) - - def extract(self): - """Destructively rips this element out of the tree.""" - if self.parent: - try: - self.parent.contents.remove(self) - except ValueError: - pass - - #Find the two elements that would be next to each other if - #this element (and any children) hadn't been parsed. Connect - #the two. - lastChild = self._lastRecursiveChild() - nextElement = lastChild.next - - if self.previous: - self.previous.next = nextElement - if nextElement: - nextElement.previous = self.previous - self.previous = None - lastChild.next = None - - self.parent = None - if self.previousSibling: - self.previousSibling.nextSibling = self.nextSibling - if self.nextSibling: - self.nextSibling.previousSibling = self.previousSibling - self.previousSibling = self.nextSibling = None - return self - - def _lastRecursiveChild(self): - "Finds the last element beneath this object to be parsed." - lastChild = self - while hasattr(lastChild, 'contents') and lastChild.contents: - lastChild = lastChild.contents[-1] - return lastChild - - def insert(self, position, newChild): - if (isinstance(newChild, basestring) - or isinstance(newChild, unicode)) \ - and not isinstance(newChild, NavigableString): - newChild = NavigableString(newChild) - - position = min(position, len(self.contents)) - if hasattr(newChild, 'parent') and newChild.parent != None: - # We're 'inserting' an element that's already one - # of this object's children. - if newChild.parent == self: - index = self.find(newChild) - if index and index < position: - # Furthermore we're moving it further down the - # list of this object's children. That means that - # when we extract this element, our target index - # will jump down one. - position = position - 1 - newChild.extract() - - newChild.parent = self - previousChild = None - if position == 0: - newChild.previousSibling = None - newChild.previous = self - else: - previousChild = self.contents[position-1] - newChild.previousSibling = previousChild - newChild.previousSibling.nextSibling = newChild - newChild.previous = previousChild._lastRecursiveChild() - if newChild.previous: - newChild.previous.next = newChild - - newChildsLastElement = newChild._lastRecursiveChild() - - if position >= len(self.contents): - newChild.nextSibling = None - - parent = self - parentsNextSibling = None - while not parentsNextSibling: - parentsNextSibling = parent.nextSibling - parent = parent.parent - if not parent: # This is the last element in the document. - break - if parentsNextSibling: - newChildsLastElement.next = parentsNextSibling - else: - newChildsLastElement.next = None - else: - nextChild = self.contents[position] - newChild.nextSibling = nextChild - if newChild.nextSibling: - newChild.nextSibling.previousSibling = newChild - newChildsLastElement.next = nextChild - - if newChildsLastElement.next: - newChildsLastElement.next.previous = newChildsLastElement - self.contents.insert(position, newChild) - - def append(self, tag): - """Appends the given tag to the contents of this tag.""" - self.insert(len(self.contents), tag) - - def findNext(self, name=None, attrs={}, text=None, **kwargs): - """Returns the first item that matches the given criteria and - appears after this Tag in the document.""" - return self._findOne(self.findAllNext, name, attrs, text, **kwargs) - - def findAllNext(self, name=None, attrs={}, text=None, limit=None, - **kwargs): - """Returns all items that match the given criteria and appear - after this Tag in the document.""" - return self._findAll(name, attrs, text, limit, self.nextGenerator, - **kwargs) - - def findNextSibling(self, name=None, attrs={}, text=None, **kwargs): - """Returns the closest sibling to this Tag that matches the - given criteria and appears after this Tag in the document.""" - return self._findOne(self.findNextSiblings, name, attrs, text, - **kwargs) - - def findNextSiblings(self, name=None, attrs={}, text=None, limit=None, - **kwargs): - """Returns the siblings of this Tag that match the given - criteria and appear after this Tag in the document.""" - return self._findAll(name, attrs, text, limit, - self.nextSiblingGenerator, **kwargs) - fetchNextSiblings = findNextSiblings # Compatibility with pre-3.x - - def findPrevious(self, name=None, attrs={}, text=None, **kwargs): - """Returns the first item that matches the given criteria and - appears before this Tag in the document.""" - return self._findOne(self.findAllPrevious, name, attrs, text, **kwargs) - - def findAllPrevious(self, name=None, attrs={}, text=None, limit=None, - **kwargs): - """Returns all items that match the given criteria and appear - before this Tag in the document.""" - return self._findAll(name, attrs, text, limit, self.previousGenerator, - **kwargs) - fetchPrevious = findAllPrevious # Compatibility with pre-3.x - - def findPreviousSibling(self, name=None, attrs={}, text=None, **kwargs): - """Returns the closest sibling to this Tag that matches the - given criteria and appears before this Tag in the document.""" - return self._findOne(self.findPreviousSiblings, name, attrs, text, - **kwargs) - - def findPreviousSiblings(self, name=None, attrs={}, text=None, - limit=None, **kwargs): - """Returns the siblings of this Tag that match the given - criteria and appear before this Tag in the document.""" - return self._findAll(name, attrs, text, limit, - self.previousSiblingGenerator, **kwargs) - fetchPreviousSiblings = findPreviousSiblings # Compatibility with pre-3.x - - def findParent(self, name=None, attrs={}, **kwargs): - """Returns the closest parent of this Tag that matches the given - criteria.""" - # NOTE: We can't use _findOne because findParents takes a different - # set of arguments. - r = None - l = self.findParents(name, attrs, 1) - if l: - r = l[0] - return r - - def findParents(self, name=None, attrs={}, limit=None, **kwargs): - """Returns the parents of this Tag that match the given - criteria.""" - - return self._findAll(name, attrs, None, limit, self.parentGenerator, - **kwargs) - fetchParents = findParents # Compatibility with pre-3.x - - #These methods do the real heavy lifting. - - def _findOne(self, method, name, attrs, text, **kwargs): - r = None - l = method(name, attrs, text, 1, **kwargs) - if l: - r = l[0] - return r - - def _findAll(self, name, attrs, text, limit, generator, **kwargs): - "Iterates over a generator looking for things that match." - - if isinstance(name, SoupStrainer): - strainer = name - else: - # Build a SoupStrainer - strainer = SoupStrainer(name, attrs, text, **kwargs) - results = ResultSet(strainer) - g = generator() - while True: - try: - i = g.next() - except StopIteration: - break - if i: - found = strainer.search(i) - if found: - results.append(found) - if limit and len(results) >= limit: - break - return results - - #These Generators can be used to navigate starting from both - #NavigableStrings and Tags. - def nextGenerator(self): - i = self - while i: - i = i.next - yield i - - def nextSiblingGenerator(self): - i = self - while i: - i = i.nextSibling - yield i - - def previousGenerator(self): - i = self - while i: - i = i.previous - yield i - - def previousSiblingGenerator(self): - i = self - while i: - i = i.previousSibling - yield i - - def parentGenerator(self): - i = self - while i: - i = i.parent - yield i - - # Utility methods - def substituteEncoding(self, str, encoding=None): - encoding = encoding or "utf-8" - return str.replace("%SOUP-ENCODING%", encoding) - - def toEncoding(self, s, encoding=None): - """Encodes an object to a string in some encoding, or to Unicode. - .""" - if isinstance(s, unicode): - if encoding: - s = s.encode(encoding) - elif isinstance(s, str): - if encoding: - s = s.encode(encoding) - else: - s = unicode(s) - else: - if encoding: - s = self.toEncoding(str(s), encoding) - else: - s = unicode(s) - return s - -class NavigableString(unicode, PageElement): - - def __new__(cls, value): - """Create a new NavigableString. - - When unpickling a NavigableString, this method is called with - the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be - passed in to the superclass's __new__ or the superclass won't know - how to handle non-ASCII characters. - """ - if isinstance(value, unicode): - return unicode.__new__(cls, value) - return unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING) - - def __getnewargs__(self): - return (unicode(self),) - - def __getattr__(self, attr): - """text.string gives you text. This is for backwards - compatibility for Navigable*String, but for CData* it lets you - get the string without the CData wrapper.""" - if attr == 'string': - return self - else: - raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, attr) - - def encode(self, encoding=DEFAULT_OUTPUT_ENCODING): - return self.decode().encode(encoding) - - def decodeGivenEventualEncoding(self, eventualEncoding): - return self - -class CData(NavigableString): - - def decodeGivenEventualEncoding(self, eventualEncoding): - return u'<![CDATA[' + self + u']]>' - -class ProcessingInstruction(NavigableString): - - def decodeGivenEventualEncoding(self, eventualEncoding): - output = self - if u'%SOUP-ENCODING%' in output: - output = self.substituteEncoding(output, eventualEncoding) - return u'<?' + output + u'?>' - -class Comment(NavigableString): - def decodeGivenEventualEncoding(self, eventualEncoding): - return u'<!--' + self + u'-->' - -class Declaration(NavigableString): - def decodeGivenEventualEncoding(self, eventualEncoding): - return u'<!' + self + u'>' - -class Tag(PageElement): - - """Represents a found HTML tag with its attributes and contents.""" - - def _invert(h): - "Cheap function to invert a hash." - i = {} - for k,v in h.items(): - i[v] = k - return i - - XML_ENTITIES_TO_SPECIAL_CHARS = { "apos" : "'", - "quot" : '"', - "amp" : "&", - "lt" : "<", - "gt" : ">" } - - XML_SPECIAL_CHARS_TO_ENTITIES = _invert(XML_ENTITIES_TO_SPECIAL_CHARS) - - def _convertEntities(self, match): - """Used in a call to re.sub to replace HTML, XML, and numeric - entities with the appropriate Unicode characters. If HTML - entities are being converted, any unrecognized entities are - escaped.""" - x = match.group(1) - if self.convertHTMLEntities and x in name2codepoint: - return unichr(name2codepoint[x]) - elif x in self.XML_ENTITIES_TO_SPECIAL_CHARS: - if self.convertXMLEntities: - return self.XML_ENTITIES_TO_SPECIAL_CHARS[x] - else: - return u'&%s;' % x - elif len(x) > 0 and x[0] == '#': - # Handle numeric entities - if len(x) > 1 and x[1] == 'x': - return unichr(int(x[2:], 16)) - else: - return unichr(int(x[1:])) - - elif self.escapeUnrecognizedEntities: - return u'&%s;' % x - else: - return u'&%s;' % x - - def __init__(self, parser, name, attrs=None, parent=None, - previous=None): - "Basic constructor." - - # We don't actually store the parser object: that lets extracted - # chunks be garbage-collected - self.parserClass = parser.__class__ - self.isSelfClosing = parser.isSelfClosingTag(name) - self.name = name - if attrs == None: - attrs = [] - self.attrs = attrs - self.contents = [] - self.setup(parent, previous) - self.hidden = False - self.containsSubstitutions = False - self.convertHTMLEntities = parser.convertHTMLEntities - self.convertXMLEntities = parser.convertXMLEntities - self.escapeUnrecognizedEntities = parser.escapeUnrecognizedEntities - - def convert(kval): - "Converts HTML, XML and numeric entities in the attribute value." - k, val = kval - if val is None: - return kval - return (k, re.sub("&(#\d+|#x[0-9a-fA-F]+|\w+);", - self._convertEntities, val)) - self.attrs = map(convert, self.attrs) - - def get(self, key, default=None): - """Returns the value of the 'key' attribute for the tag, or - the value given for 'default' if it doesn't have that - attribute.""" - return self._getAttrMap().get(key, default) - - def has_key(self, key): - return self._getAttrMap().has_key(key) - - def __getitem__(self, key): - """tag[key] returns the value of the 'key' attribute for the tag, - and throws an exception if it's not there.""" - return self._getAttrMap()[key] - - def __iter__(self): - "Iterating over a tag iterates over its contents." - return iter(self.contents) - - def __len__(self): - "The length of a tag is the length of its list of contents." - return len(self.contents) - - def __contains__(self, x): - return x in self.contents - - def __nonzero__(self): - "A tag is non-None even if it has no contents." - return True - - def __setitem__(self, key, value): - """Setting tag[key] sets the value of the 'key' attribute for the - tag.""" - self._getAttrMap() - self.attrMap[key] = value - found = False - for i in range(0, len(self.attrs)): - if self.attrs[i][0] == key: - self.attrs[i] = (key, value) - found = True - if not found: - self.attrs.append((key, value)) - self._getAttrMap()[key] = value - - def __delitem__(self, key): - "Deleting tag[key] deletes all 'key' attributes for the tag." - for item in self.attrs: - if item[0] == key: - self.attrs.remove(item) - #We don't break because bad HTML can define the same - #attribute multiple times. - self._getAttrMap() - if self.attrMap.has_key(key): - del self.attrMap[key] - - def __call__(self, *args, **kwargs): - """Calling a tag like a function is the same as calling its - findAll() method. Eg. tag('a') returns a list of all the A tags - found within this tag.""" - return apply(self.findAll, args, kwargs) - - def __getattr__(self, tag): - #print "Getattr %s.%s" % (self.__class__, tag) - if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3: - return self.find(tag[:-3]) - elif tag.find('__') != 0: - return self.find(tag) - raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__, tag) - - def __eq__(self, other): - """Returns true iff this tag has the same name, the same attributes, - and the same contents (recursively) as the given tag. - - NOTE: right now this will return false if two tags have the - same attributes in a different order. Should this be fixed?""" - if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or self.name != other.name or self.attrs != other.attrs or len(self) != len(other): - return False - for i in range(0, len(self.contents)): - if self.contents[i] != other.contents[i]: - return False - return True - - def __ne__(self, other): - """Returns true iff this tag is not identical to the other tag, - as defined in __eq__.""" - return not self == other - - def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING): - """Renders this tag as a string.""" - return self.decode(eventualEncoding=encoding) - - BARE_AMPERSAND_OR_BRACKET = re.compile("([<>]|" - + "&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)" - + ")") - - def _sub_entity(self, x): - """Used with a regular expression to substitute the - appropriate XML entity for an XML special character.""" - return "&" + self.XML_SPECIAL_CHARS_TO_ENTITIES[x.group(0)[0]] + ";" - - def __unicode__(self): - return self.decode() - - def __str__(self): - return self.encode() - - def encode(self, encoding=DEFAULT_OUTPUT_ENCODING, - prettyPrint=False, indentLevel=0): - return self.decode(prettyPrint, indentLevel, encoding).encode(encoding) - - def decode(self, prettyPrint=False, indentLevel=0, - eventualEncoding=DEFAULT_OUTPUT_ENCODING): - """Returns a string or Unicode representation of this tag and - its contents. To get Unicode, pass None for encoding.""" - - attrs = [] - if self.attrs: - for key, val in self.attrs: - fmt = '%s="%s"' - if isString(val): - if (self.containsSubstitutions - and eventualEncoding is not None - and '%SOUP-ENCODING%' in val): - val = self.substituteEncoding(val, eventualEncoding) - - # The attribute value either: - # - # * Contains no embedded double quotes or single quotes. - # No problem: we enclose it in double quotes. - # * Contains embedded single quotes. No problem: - # double quotes work here too. - # * Contains embedded double quotes. No problem: - # we enclose it in single quotes. - # * Embeds both single _and_ double quotes. This - # can't happen naturally, but it can happen if - # you modify an attribute value after parsing - # the document. Now we have a bit of a - # problem. We solve it by enclosing the - # attribute in single quotes, and escaping any - # embedded single quotes to XML entities. - if '"' in val: - fmt = "%s='%s'" - if "'" in val: - # TODO: replace with apos when - # appropriate. - val = val.replace("'", "&squot;") - - # Now we're okay w/r/t quotes. But the attribute - # value might also contain angle brackets, or - # ampersands that aren't part of entities. We need - # to escape those to XML entities too. - val = self.BARE_AMPERSAND_OR_BRACKET.sub(self._sub_entity, val) - if val is None: - # Handle boolean attributes. - decoded = key - else: - decoded = fmt % (key, val) - attrs.append(decoded) - close = '' - closeTag = '' - if self.isSelfClosing: - close = ' /' - else: - closeTag = '</%s>' % self.name - - indentTag, indentContents = 0, 0 - if prettyPrint: - indentTag = indentLevel - space = (' ' * (indentTag-1)) - indentContents = indentTag + 1 - contents = self.decodeContents(prettyPrint, indentContents, - eventualEncoding) - if self.hidden: - s = contents - else: - s = [] - attributeString = '' - if attrs: - attributeString = ' ' + ' '.join(attrs) - if prettyPrint: - s.append(space) - s.append('<%s%s%s>' % (self.name, attributeString, close)) - if prettyPrint: - s.append("\n") - s.append(contents) - if prettyPrint and contents and contents[-1] != "\n": - s.append("\n") - if prettyPrint and closeTag: - s.append(space) - s.append(closeTag) - if prettyPrint and closeTag and self.nextSibling: - s.append("\n") - s = ''.join(s) - return s - - def decompose(self): - """Recursively destroys the contents of this tree.""" - contents = [i for i in self.contents] - for i in contents: - if isinstance(i, Tag): - i.decompose() - else: - i.extract() - self.extract() - - def prettify(self, encoding=DEFAULT_OUTPUT_ENCODING): - return self.encode(encoding, True) - - def encodeContents(self, encoding=DEFAULT_OUTPUT_ENCODING, - prettyPrint=False, indentLevel=0): - return self.decodeContents(prettyPrint, indentLevel).encode(encoding) - - def decodeContents(self, prettyPrint=False, indentLevel=0, - eventualEncoding=DEFAULT_OUTPUT_ENCODING): - """Renders the contents of this tag as a string in the given - encoding. If encoding is None, returns a Unicode string..""" - s=[] - for c in self: - text = None - if isinstance(c, NavigableString): - text = c.decodeGivenEventualEncoding(eventualEncoding) - elif isinstance(c, Tag): - s.append(c.decode(prettyPrint, indentLevel, eventualEncoding)) - if text and prettyPrint: - text = text.strip() - if text: - if prettyPrint: - s.append(" " * (indentLevel-1)) - s.append(text) - if prettyPrint: - s.append("\n") - return ''.join(s) - - #Soup methods - - def find(self, name=None, attrs={}, recursive=True, text=None, - **kwargs): - """Return only the first child of this Tag matching the given - criteria.""" - r = None - l = self.findAll(name, attrs, recursive, text, 1, **kwargs) - if l: - r = l[0] - return r - findChild = find - - def findAll(self, name=None, attrs={}, recursive=True, text=None, - limit=None, **kwargs): - """Extracts a list of Tag objects that match the given - criteria. You can specify the name of the Tag and any - attributes you want the Tag to have. - - The value of a key-value pair in the 'attrs' map can be a - string, a list of strings, a regular expression object, or a - callable that takes a string and returns whether or not the - string matches for some custom definition of 'matches'. The - same is true of the tag name.""" - generator = self.recursiveChildGenerator - if not recursive: - generator = self.childGenerator - return self._findAll(name, attrs, text, limit, generator, **kwargs) - findChildren = findAll - - # Pre-3.x compatibility methods. Will go away in 4.0. - first = find - fetch = findAll - - def fetchText(self, text=None, recursive=True, limit=None): - return self.findAll(text=text, recursive=recursive, limit=limit) - - def firstText(self, text=None, recursive=True): - return self.find(text=text, recursive=recursive) - - # 3.x compatibility methods. Will go away in 4.0. - def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING, - prettyPrint=False, indentLevel=0): - if encoding is None: - return self.decodeContents(prettyPrint, indentLevel, encoding) - else: - return self.encodeContents(encoding, prettyPrint, indentLevel) - - - #Private methods - - def _getAttrMap(self): - """Initializes a map representation of this tag's attributes, - if not already initialized.""" - if not getattr(self, 'attrMap'): - self.attrMap = {} - for (key, value) in self.attrs: - self.attrMap[key] = value - return self.attrMap - - #Generator methods - def recursiveChildGenerator(self): - if not len(self.contents): - raise StopIteration - stopNode = self._lastRecursiveChild().next - current = self.contents[0] - while current is not stopNode: - yield current - current = current.next - - def childGenerator(self): - if not len(self.contents): - raise StopIteration - current = self.contents[0] - while current: - yield current - current = current.nextSibling - raise StopIteration - -# Next, a couple classes to represent queries and their results. -class SoupStrainer: - """Encapsulates a number of ways of matching a markup element (tag or - text).""" - - def __init__(self, name=None, attrs={}, text=None, **kwargs): - self.name = name - if isString(attrs): - kwargs['class'] = attrs - attrs = None - if kwargs: - if attrs: - attrs = attrs.copy() - attrs.update(kwargs) - else: - attrs = kwargs - self.attrs = attrs - self.text = text - - def __str__(self): - if self.text: - return self.text - else: - return "%s|%s" % (self.name, self.attrs) - - def searchTag(self, markupName=None, markupAttrs={}): - found = None - markup = None - if isinstance(markupName, Tag): - markup = markupName - markupAttrs = markup - callFunctionWithTagData = callable(self.name) \ - and not isinstance(markupName, Tag) - - if (not self.name) \ - or callFunctionWithTagData \ - or (markup and self._matches(markup, self.name)) \ - or (not markup and self._matches(markupName, self.name)): - if callFunctionWithTagData: - match = self.name(markupName, markupAttrs) - else: - match = True - markupAttrMap = None - for attr, matchAgainst in self.attrs.items(): - if not markupAttrMap: - if hasattr(markupAttrs, 'get'): - markupAttrMap = markupAttrs - else: - markupAttrMap = {} - for k,v in markupAttrs: - markupAttrMap[k] = v - attrValue = markupAttrMap.get(attr) - if not self._matches(attrValue, matchAgainst): - match = False - break - if match: - if markup: - found = markup - else: - found = markupName - return found - - def search(self, markup): - #print 'looking for %s in %s' % (self, markup) - found = None - # If given a list of items, scan it for a text element that - # matches. - if isList(markup) and not isinstance(markup, Tag): - for element in markup: - if isinstance(element, NavigableString) \ - and self.search(element): - found = element - break - # If it's a Tag, make sure its name or attributes match. - # Don't bother with Tags if we're searching for text. - elif isinstance(markup, Tag): - if not self.text: - found = self.searchTag(markup) - # If it's text, make sure the text matches. - elif isinstance(markup, NavigableString) or \ - isString(markup): - if self._matches(markup, self.text): - found = markup - else: - raise Exception, "I don't know how to match against a %s" \ - % markup.__class__ - return found - - def _matches(self, markup, matchAgainst): - #print "Matching %s against %s" % (markup, matchAgainst) - result = False - if matchAgainst == True and type(matchAgainst) == types.BooleanType: - result = markup != None - elif callable(matchAgainst): - result = matchAgainst(markup) - else: - #Custom match methods take the tag as an argument, but all - #other ways of matching match the tag name as a string. - if isinstance(markup, Tag): - markup = markup.name - if markup is not None and not isString(markup): - markup = unicode(markup) - #Now we know that chunk is either a string, or None. - if hasattr(matchAgainst, 'match'): - # It's a regexp object. - result = markup and matchAgainst.search(markup) - elif (isList(matchAgainst) - and (markup is not None or not isString(matchAgainst))): - result = markup in matchAgainst - elif hasattr(matchAgainst, 'items'): - result = markup.has_key(matchAgainst) - elif matchAgainst and isString(markup): - if isinstance(markup, unicode): - matchAgainst = unicode(matchAgainst) - else: - matchAgainst = str(matchAgainst) - - if not result: - result = matchAgainst == markup - return result - -class ResultSet(list): - """A ResultSet is just a list that keeps track of the SoupStrainer - that created it.""" - def __init__(self, source): - list.__init__([]) - self.source = source - -# Now, some helper functions. - -def isList(l): - """Convenience method that works with all 2.x versions of Python - to determine whether or not something is listlike.""" - return ((hasattr(l, '__iter__') and not isString(l)) - or (type(l) in (types.ListType, types.TupleType))) - -def isString(s): - """Convenience method that works with all 2.x versions of Python - to determine whether or not something is stringlike.""" - try: - return isinstance(s, unicode) or isinstance(s, basestring) - except NameError: - return isinstance(s, str) - -def buildTagMap(default, *args): - """Turns a list of maps, lists, or scalars into a single map. - Used to build the SELF_CLOSING_TAGS, NESTABLE_TAGS, and - NESTING_RESET_TAGS maps out of lists and partial maps.""" - built = {} - for portion in args: - if hasattr(portion, 'items'): - #It's a map. Merge it. - for k,v in portion.items(): - built[k] = v - elif isList(portion) and not isString(portion): - #It's a list. Map each item to the default. - for k in portion: - built[k] = default - else: - #It's a scalar. Map it to the default. - built[portion] = default - return built - -# Now, the parser classes. - -class HTMLParserBuilder(HTMLParser): - - def __init__(self, soup): - HTMLParser.__init__(self) - self.soup = soup - - # We inherit feed() and reset(). - - def handle_starttag(self, name, attrs): - if name == 'meta': - self.soup.extractCharsetFromMeta(attrs) - else: - self.soup.unknown_starttag(name, attrs) - - def handle_endtag(self, name): - self.soup.unknown_endtag(name) - - def handle_data(self, content): - self.soup.handle_data(content) - - def _toStringSubclass(self, text, subclass): - """Adds a certain piece of text to the tree as a NavigableString - subclass.""" - self.soup.endData() - self.handle_data(text) - self.soup.endData(subclass) - - def handle_pi(self, text): - """Handle a processing instruction as a ProcessingInstruction - object, possibly one with a %SOUP-ENCODING% slot into which an - encoding will be plugged later.""" - if text[:3] == "xml": - text = u"xml version='1.0' encoding='%SOUP-ENCODING%'" - self._toStringSubclass(text, ProcessingInstruction) - - def handle_comment(self, text): - "Handle comments as Comment objects." - self._toStringSubclass(text, Comment) - - def handle_charref(self, ref): - "Handle character references as data." - if self.soup.convertEntities: - data = unichr(int(ref)) - else: - data = '&#%s;' % ref - self.handle_data(data) - - def handle_entityref(self, ref): - """Handle entity references as data, possibly converting known - HTML and/or XML entity references to the corresponding Unicode - characters.""" - data = None - if self.soup.convertHTMLEntities: - try: - data = unichr(name2codepoint[ref]) - except KeyError: - pass - - if not data and self.soup.convertXMLEntities: - data = self.soup.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref) - - if not data and self.soup.convertHTMLEntities and \ - not self.soup.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref): - # TODO: We've got a problem here. We're told this is - # an entity reference, but it's not an XML entity - # reference or an HTML entity reference. Nonetheless, - # the logical thing to do is to pass it through as an - # unrecognized entity reference. - # - # Except: when the input is "&carol;" this function - # will be called with input "carol". When the input is - # "AT&T", this function will be called with input - # "T". We have no way of knowing whether a semicolon - # was present originally, so we don't know whether - # this is an unknown entity or just a misplaced - # ampersand. - # - # The more common case is a misplaced ampersand, so I - # escape the ampersand and omit the trailing semicolon. - data = "&%s" % ref - if not data: - # This case is different from the one above, because we - # haven't already gone through a supposedly comprehensive - # mapping of entities to Unicode characters. We might not - # have gone through any mapping at all. So the chances are - # very high that this is a real entity, and not a - # misplaced ampersand. - data = "&%s;" % ref - self.handle_data(data) - - def handle_decl(self, data): - "Handle DOCTYPEs and the like as Declaration objects." - self._toStringSubclass(data, Declaration) - - def parse_declaration(self, i): - """Treat a bogus SGML declaration as raw data. Treat a CDATA - declaration as a CData object.""" - j = None - if self.rawdata[i:i+9] == '<![CDATA[': - k = self.rawdata.find(']]>', i) - if k == -1: - k = len(self.rawdata) - data = self.rawdata[i+9:k] - j = k+3 - self._toStringSubclass(data, CData) - else: - try: - j = HTMLParser.parse_declaration(self, i) - except HTMLParseError: - toHandle = self.rawdata[i:] - self.handle_data(toHandle) - j = i + len(toHandle) - return j - - -class BeautifulStoneSoup(Tag): - - """This class contains the basic parser and search code. It defines - a parser that knows nothing about tag behavior except for the - following: - - You can't close a tag without closing all the tags it encloses. - That is, "<foo><bar></foo>" actually means - "<foo><bar></bar></foo>". - - [Another possible explanation is "<foo><bar /></foo>", but since - this class defines no SELF_CLOSING_TAGS, it will never use that - explanation.] - - This class is useful for parsing XML or made-up markup languages, - or when BeautifulSoup makes an assumption counter to what you were - expecting.""" - - SELF_CLOSING_TAGS = {} - NESTABLE_TAGS = {} - RESET_NESTING_TAGS = {} - QUOTE_TAGS = {} - PRESERVE_WHITESPACE_TAGS = [] - - MARKUP_MASSAGE = [(re.compile('(<[^<>]*)/>'), - lambda x: x.group(1) + ' />'), - (re.compile('<!\s+([^<>]*)>'), - lambda x: '<!' + x.group(1) + '>') - ] - - ROOT_TAG_NAME = u'[document]' - - HTML_ENTITIES = "html" - XML_ENTITIES = "xml" - XHTML_ENTITIES = "xhtml" - # TODO: This only exists for backwards-compatibility - ALL_ENTITIES = XHTML_ENTITIES - - # Used when determining whether a text node is all whitespace and - # can be replaced with a single space. A text node that contains - # fancy Unicode spaces (usually non-breaking) should be left - # alone. - STRIP_ASCII_SPACES = { 9: None, 10: None, 12: None, 13: None, 32: None, } - - def __init__(self, markup="", parseOnlyThese=None, fromEncoding=None, - markupMassage=True, smartQuotesTo=XML_ENTITIES, - convertEntities=None, selfClosingTags=None, isHTML=False, - builder=HTMLParserBuilder): - """The Soup object is initialized as the 'root tag', and the - provided markup (which can be a string or a file-like object) - is fed into the underlying parser. - - HTMLParser will process most bad HTML, and the BeautifulSoup - class has some tricks for dealing with some HTML that kills - HTMLParser, but Beautiful Soup can nonetheless choke or lose data - if your data uses self-closing tags or declarations - incorrectly. - - By default, Beautiful Soup uses regexes to sanitize input, - avoiding the vast majority of these problems. If the problems - don't apply to you, pass in False for markupMassage, and - you'll get better performance. - - The default parser massage techniques fix the two most common - instances of invalid HTML that choke HTMLParser: - - <br/> (No space between name of closing tag and tag close) - <! --Comment--> (Extraneous whitespace in declaration) - - You can pass in a custom list of (RE object, replace method) - tuples to get Beautiful Soup to scrub your input the way you - want.""" - - self.parseOnlyThese = parseOnlyThese - self.fromEncoding = fromEncoding - self.smartQuotesTo = smartQuotesTo - self.convertEntities = convertEntities - # Set the rules for how we'll deal with the entities we - # encounter - if self.convertEntities: - # It doesn't make sense to convert encoded characters to - # entities even while you're converting entities to Unicode. - # Just convert it all to Unicode. - self.smartQuotesTo = None - if convertEntities == self.HTML_ENTITIES: - self.convertXMLEntities = False - self.convertHTMLEntities = True - self.escapeUnrecognizedEntities = True - elif convertEntities == self.XHTML_ENTITIES: - self.convertXMLEntities = True - self.convertHTMLEntities = True - self.escapeUnrecognizedEntities = False - elif convertEntities == self.XML_ENTITIES: - self.convertXMLEntities = True - self.convertHTMLEntities = False - self.escapeUnrecognizedEntities = False - else: - self.convertXMLEntities = False - self.convertHTMLEntities = False - self.escapeUnrecognizedEntities = False - - self.instanceSelfClosingTags = buildTagMap(None, selfClosingTags) - self.builder = builder(self) - self.reset() - - if hasattr(markup, 'read'): # It's a file-type object. - markup = markup.read() - self.markup = markup - self.markupMassage = markupMassage - try: - self._feed(isHTML=isHTML) - except StopParsing: - pass - self.markup = None # The markup can now be GCed. - self.builder = None # So can the builder. - - def _feed(self, inDocumentEncoding=None, isHTML=False): - # Convert the document to Unicode. - markup = self.markup - if isinstance(markup, unicode): - if not hasattr(self, 'originalEncoding'): - self.originalEncoding = None - else: - dammit = UnicodeDammit\ - (markup, [self.fromEncoding, inDocumentEncoding], - smartQuotesTo=self.smartQuotesTo, isHTML=isHTML) - markup = dammit.unicode - self.originalEncoding = dammit.originalEncoding - self.declaredHTMLEncoding = dammit.declaredHTMLEncoding - if markup: - if self.markupMassage: - if not isList(self.markupMassage): - self.markupMassage = self.MARKUP_MASSAGE - for fix, m in self.markupMassage: - markup = fix.sub(m, markup) - # TODO: We get rid of markupMassage so that the - # soup object can be deepcopied later on. Some - # Python installations can't copy regexes. If anyone - # was relying on the existence of markupMassage, this - # might cause problems. - del(self.markupMassage) - self.builder.reset() - - self.builder.feed(markup) - # Close out any unfinished strings and close all the open tags. - self.endData() - while self.currentTag.name != self.ROOT_TAG_NAME: - self.popTag() - - def isSelfClosingTag(self, name): - """Returns true iff the given string is the name of a - self-closing tag according to this parser.""" - return self.SELF_CLOSING_TAGS.has_key(name) \ - or self.instanceSelfClosingTags.has_key(name) - - def reset(self): - Tag.__init__(self, self, self.ROOT_TAG_NAME) - self.hidden = 1 - self.builder.reset() - self.currentData = [] - self.currentTag = None - self.tagStack = [] - self.quoteStack = [] - self.pushTag(self) - - def popTag(self): - tag = self.tagStack.pop() - # Tags with just one string-owning child get the child as a - # 'string' property, so that soup.tag.string is shorthand for - # soup.tag.contents[0] - if len(self.currentTag.contents) == 1 and \ - isinstance(self.currentTag.contents[0], NavigableString): - self.currentTag.string = self.currentTag.contents[0] - - #print "Pop", tag.name - if self.tagStack: - self.currentTag = self.tagStack[-1] - return self.currentTag - - def pushTag(self, tag): - #print "Push", tag.name - if self.currentTag: - self.currentTag.contents.append(tag) - self.tagStack.append(tag) - self.currentTag = self.tagStack[-1] - - def endData(self, containerClass=NavigableString): - if self.currentData: - currentData = u''.join(self.currentData) - if (currentData.translate(self.STRIP_ASCII_SPACES) == '' and - not set([tag.name for tag in self.tagStack]).intersection( - self.PRESERVE_WHITESPACE_TAGS)): - if '\n' in currentData: - currentData = '\n' - else: - currentData = ' ' - self.currentData = [] - if self.parseOnlyThese and len(self.tagStack) <= 1 and \ - (not self.parseOnlyThese.text or \ - not self.parseOnlyThese.search(currentData)): - return - o = containerClass(currentData) - o.setup(self.currentTag, self.previous) - if self.previous: - self.previous.next = o - self.previous = o - self.currentTag.contents.append(o) - - - def _popToTag(self, name, inclusivePop=True): - """Pops the tag stack up to and including the most recent - instance of the given tag. If inclusivePop is false, pops the tag - stack up to but *not* including the most recent instqance of - the given tag.""" - #print "Popping to %s" % name - if name == self.ROOT_TAG_NAME: - return - - numPops = 0 - mostRecentTag = None - for i in range(len(self.tagStack)-1, 0, -1): - if name == self.tagStack[i].name: - numPops = len(self.tagStack)-i - break - if not inclusivePop: - numPops = numPops - 1 - - for i in range(0, numPops): - mostRecentTag = self.popTag() - return mostRecentTag - - def _smartPop(self, name): - - """We need to pop up to the previous tag of this type, unless - one of this tag's nesting reset triggers comes between this - tag and the previous tag of this type, OR unless this tag is a - generic nesting trigger and another generic nesting trigger - comes between this tag and the previous tag of this type. - - Examples: - <p>Foo<b>Bar *<p>* should pop to 'p', not 'b'. - <p>Foo<table>Bar *<p>* should pop to 'table', not 'p'. - <p>Foo<table><tr>Bar *<p>* should pop to 'tr', not 'p'. - - <li><ul><li> *<li>* should pop to 'ul', not the first 'li'. - <tr><table><tr> *<tr>* should pop to 'table', not the first 'tr' - <td><tr><td> *<td>* should pop to 'tr', not the first 'td' - """ - - nestingResetTriggers = self.NESTABLE_TAGS.get(name) - isNestable = nestingResetTriggers != None - isResetNesting = self.RESET_NESTING_TAGS.has_key(name) - popTo = None - inclusive = True - for i in range(len(self.tagStack)-1, 0, -1): - p = self.tagStack[i] - if (not p or p.name == name) and not isNestable: - #Non-nestable tags get popped to the top or to their - #last occurance. - popTo = name - break - if (nestingResetTriggers != None - and p.name in nestingResetTriggers) \ - or (nestingResetTriggers == None and isResetNesting - and self.RESET_NESTING_TAGS.has_key(p.name)): - - #If we encounter one of the nesting reset triggers - #peculiar to this tag, or we encounter another tag - #that causes nesting to reset, pop up to but not - #including that tag. - popTo = p.name - inclusive = False - break - p = p.parent - if popTo: - self._popToTag(popTo, inclusive) - - def unknown_starttag(self, name, attrs, selfClosing=0): - #print "Start tag %s: %s" % (name, attrs) - if self.quoteStack: - #This is not a real tag. - #print "<%s> is not real!" % name - attrs = ''.join(map(lambda(x, y): ' %s="%s"' % (x, y), attrs)) - self.handle_data('<%s%s>' % (name, attrs)) - return - self.endData() - - if not self.isSelfClosingTag(name) and not selfClosing: - self._smartPop(name) - - if self.parseOnlyThese and len(self.tagStack) <= 1 \ - and (self.parseOnlyThese.text or not self.parseOnlyThese.searchTag(name, attrs)): - return - - tag = Tag(self, name, attrs, self.currentTag, self.previous) - if self.previous: - self.previous.next = tag - self.previous = tag - self.pushTag(tag) - if selfClosing or self.isSelfClosingTag(name): - self.popTag() - if name in self.QUOTE_TAGS: - #print "Beginning quote (%s)" % name - self.quoteStack.append(name) - self.literal = 1 - return tag - - def unknown_endtag(self, name): - #print "End tag %s" % name - if self.quoteStack and self.quoteStack[-1] != name: - #This is not a real end tag. - #print "</%s> is not real!" % name - self.handle_data('</%s>' % name) - return - self.endData() - self._popToTag(name) - if self.quoteStack and self.quoteStack[-1] == name: - self.quoteStack.pop() - self.literal = (len(self.quoteStack) > 0) - - def handle_data(self, data): - self.currentData.append(data) - - def extractCharsetFromMeta(self, attrs): - self.unknown_starttag('meta', attrs) - - -class BeautifulSoup(BeautifulStoneSoup): - - """This parser knows the following facts about HTML: - - * Some tags have no closing tag and should be interpreted as being - closed as soon as they are encountered. - - * The text inside some tags (ie. 'script') may contain tags which - are not really part of the document and which should be parsed - as text, not tags. If you want to parse the text as tags, you can - always fetch it and parse it explicitly. - - * Tag nesting rules: - - Most tags can't be nested at all. For instance, the occurance of - a <p> tag should implicitly close the previous <p> tag. - - <p>Para1<p>Para2 - should be transformed into: - <p>Para1</p><p>Para2 - - Some tags can be nested arbitrarily. For instance, the occurance - of a <blockquote> tag should _not_ implicitly close the previous - <blockquote> tag. - - Alice said: <blockquote>Bob said: <blockquote>Blah - should NOT be transformed into: - Alice said: <blockquote>Bob said: </blockquote><blockquote>Blah - - Some tags can be nested, but the nesting is reset by the - interposition of other tags. For instance, a <tr> tag should - implicitly close the previous <tr> tag within the same <table>, - but not close a <tr> tag in another table. - - <table><tr>Blah<tr>Blah - should be transformed into: - <table><tr>Blah</tr><tr>Blah - but, - <tr>Blah<table><tr>Blah - should NOT be transformed into - <tr>Blah<table></tr><tr>Blah - - Differing assumptions about tag nesting rules are a major source - of problems with the BeautifulSoup class. If BeautifulSoup is not - treating as nestable a tag your page author treats as nestable, - try ICantBelieveItsBeautifulSoup, MinimalSoup, or - BeautifulStoneSoup before writing your own subclass.""" - - def __init__(self, *args, **kwargs): - if not kwargs.has_key('smartQuotesTo'): - kwargs['smartQuotesTo'] = self.HTML_ENTITIES - kwargs['isHTML'] = True - BeautifulStoneSoup.__init__(self, *args, **kwargs) - - SELF_CLOSING_TAGS = buildTagMap(None, - ['br' , 'hr', 'input', 'img', 'meta', - 'spacer', 'link', 'frame', 'base']) - - PRESERVE_WHITESPACE_TAGS = set(['pre', 'textarea']) - - QUOTE_TAGS = {'script' : None, 'textarea' : None} - - #According to the HTML standard, each of these inline tags can - #contain another tag of the same type. Furthermore, it's common - #to actually use these tags this way. - NESTABLE_INLINE_TAGS = ['span', 'font', 'q', 'object', 'bdo', 'sub', 'sup', - 'center'] - - #According to the HTML standard, these block tags can contain - #another tag of the same type. Furthermore, it's common - #to actually use these tags this way. - NESTABLE_BLOCK_TAGS = ['blockquote', 'div', 'fieldset', 'ins', 'del'] - - #Lists can contain other lists, but there are restrictions. - NESTABLE_LIST_TAGS = { 'ol' : [], - 'ul' : [], - 'li' : ['ul', 'ol'], - 'dl' : [], - 'dd' : ['dl'], - 'dt' : ['dl'] } - - #Tables can contain other tables, but there are restrictions. - NESTABLE_TABLE_TAGS = {'table' : [], - 'tr' : ['table', 'tbody', 'tfoot', 'thead'], - 'td' : ['tr'], - 'th' : ['tr'], - 'thead' : ['table'], - 'tbody' : ['table'], - 'tfoot' : ['table'], - } - - NON_NESTABLE_BLOCK_TAGS = ['address', 'form', 'p', 'pre'] - - #If one of these tags is encountered, all tags up to the next tag of - #this type are popped. - RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript', - NON_NESTABLE_BLOCK_TAGS, - NESTABLE_LIST_TAGS, - NESTABLE_TABLE_TAGS) - - NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS, - NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS) - - # Used to detect the charset in a META tag; see start_meta - CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)", re.M) - - def extractCharsetFromMeta(self, attrs): - """Beautiful Soup can detect a charset included in a META tag, - try to convert the document to that charset, and re-parse the - document from the beginning.""" - httpEquiv = None - contentType = None - contentTypeIndex = None - tagNeedsEncodingSubstitution = False - - for i in range(0, len(attrs)): - key, value = attrs[i] - key = key.lower() - if key == 'http-equiv': - httpEquiv = value - elif key == 'content': - contentType = value - contentTypeIndex = i - - if httpEquiv and contentType: # It's an interesting meta tag. - match = self.CHARSET_RE.search(contentType) - if match: - if (self.declaredHTMLEncoding is not None or - self.originalEncoding == self.fromEncoding): - # An HTML encoding was sniffed while converting - # the document to Unicode, or an HTML encoding was - # sniffed during a previous pass through the - # document, or an encoding was specified - # explicitly and it worked. Rewrite the meta tag. - def rewrite(match): - return match.group(1) + "%SOUP-ENCODING%" - newAttr = self.CHARSET_RE.sub(rewrite, contentType) - attrs[contentTypeIndex] = (attrs[contentTypeIndex][0], - newAttr) - tagNeedsEncodingSubstitution = True - else: - # This is our first pass through the document. - # Go through it again with the encoding information. - newCharset = match.group(3) - if newCharset and newCharset != self.originalEncoding: - self.declaredHTMLEncoding = newCharset - self._feed(self.declaredHTMLEncoding) - raise StopParsing - pass - tag = self.unknown_starttag("meta", attrs) - if tag and tagNeedsEncodingSubstitution: - tag.containsSubstitutions = True - - -class StopParsing(Exception): - pass - -class ICantBelieveItsBeautifulSoup(BeautifulSoup): - - """The BeautifulSoup class is oriented towards skipping over - common HTML errors like unclosed tags. However, sometimes it makes - errors of its own. For instance, consider this fragment: - - <b>Foo<b>Bar</b></b> - - This is perfectly valid (if bizarre) HTML. However, the - BeautifulSoup class will implicitly close the first b tag when it - encounters the second 'b'. It will think the author wrote - "<b>Foo<b>Bar", and didn't close the first 'b' tag, because - there's no real-world reason to bold something that's already - bold. When it encounters '</b></b>' it will close two more 'b' - tags, for a grand total of three tags closed instead of two. This - can throw off the rest of your document structure. The same is - true of a number of other tags, listed below. - - It's much more common for someone to forget to close a 'b' tag - than to actually use nested 'b' tags, and the BeautifulSoup class - handles the common case. This class handles the not-co-common - case: where you can't believe someone wrote what they did, but - it's valid HTML and BeautifulSoup screwed up by assuming it - wouldn't be.""" - - I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \ - ['em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong', - 'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b', - 'big'] - - I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ['noscript'] - - NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS, - I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS, - I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS) - -class MinimalSoup(BeautifulSoup): - """The MinimalSoup class is for parsing HTML that contains - pathologically bad markup. It makes no assumptions about tag - nesting, but it does know which tags are self-closing, that - <script> tags contain Javascript and should not be parsed, that - META tags may contain encoding information, and so on. - - This also makes it better for subclassing than BeautifulStoneSoup - or BeautifulSoup.""" - - RESET_NESTING_TAGS = buildTagMap('noscript') - NESTABLE_TAGS = {} - -class BeautifulSOAP(BeautifulStoneSoup): - """This class will push a tag with only a single string child into - the tag's parent as an attribute. The attribute's name is the tag - name, and the value is the string child. An example should give - the flavor of the change: - - <foo><bar>baz</bar></foo> - => - <foo bar="baz"><bar>baz</bar></foo> - - You can then access fooTag['bar'] instead of fooTag.barTag.string. - - This is, of course, useful for scraping structures that tend to - use subelements instead of attributes, such as SOAP messages. Note - that it modifies its input, so don't print the modified version - out. - - I'm not sure how many people really want to use this class; let me - know if you do. Mainly I like the name.""" - - def popTag(self): - if len(self.tagStack) > 1: - tag = self.tagStack[-1] - parent = self.tagStack[-2] - parent._getAttrMap() - if (isinstance(tag, Tag) and len(tag.contents) == 1 and - isinstance(tag.contents[0], NavigableString) and - not parent.attrMap.has_key(tag.name)): - parent[tag.name] = tag.contents[0] - BeautifulStoneSoup.popTag(self) - -#Enterprise class names! It has come to our attention that some people -#think the names of the Beautiful Soup parser classes are too silly -#and "unprofessional" for use in enterprise screen-scraping. We feel -#your pain! For such-minded folk, the Beautiful Soup Consortium And -#All-Night Kosher Bakery recommends renaming this file to -#"RobustParser.py" (or, in cases of extreme enterprisiness, -#"RobustParserBeanInterface.class") and using the following -#enterprise-friendly class aliases: -class RobustXMLParser(BeautifulStoneSoup): - pass -class RobustHTMLParser(BeautifulSoup): - pass -class RobustWackAssHTMLParser(ICantBelieveItsBeautifulSoup): - pass -class RobustInsanelyWackAssHTMLParser(MinimalSoup): - pass -class SimplifyingSOAPParser(BeautifulSOAP): - pass - -###################################################### -# -# Bonus library: Unicode, Dammit -# -# This class forces XML data into a standard format (usually to UTF-8 -# or Unicode). It is heavily based on code from Mark Pilgrim's -# Universal Feed Parser. It does not rewrite the XML or HTML to -# reflect a new encoding: that happens in BeautifulStoneSoup.handle_pi -# (XML) and BeautifulSoup.start_meta (HTML). - -# Autodetects character encodings. -# Download from http://chardet.feedparser.org/ -try: - import chardet -# import chardet.constants -# chardet.constants._debug = 1 -except ImportError: - chardet = None - -# cjkcodecs and iconv_codec make Python know about more character encodings. -# Both are available from http://cjkpython.i18n.org/ -# They're built in if you use Python 2.4. -try: - import cjkcodecs.aliases -except ImportError: - pass -try: - import iconv_codec -except ImportError: - pass - -class UnicodeDammit: - """A class for detecting the encoding of a *ML document and - converting it to a Unicode string. If the source encoding is - windows-1252, can replace MS smart quotes with their HTML or XML - equivalents.""" - - # This dictionary maps commonly seen values for "charset" in HTML - # meta tags to the corresponding Python codec names. It only covers - # values that aren't in Python's aliases and can't be determined - # by the heuristics in find_codec. - CHARSET_ALIASES = { "macintosh" : "mac-roman", - "x-sjis" : "shift-jis" } - - def __init__(self, markup, overrideEncodings=[], - smartQuotesTo='xml', isHTML=False): - self.declaredHTMLEncoding = None - self.markup, documentEncoding, sniffedEncoding = \ - self._detectEncoding(markup, isHTML) - self.smartQuotesTo = smartQuotesTo - self.triedEncodings = [] - if markup == '' or isinstance(markup, unicode): - self.originalEncoding = None - self.unicode = unicode(markup) - return - - u = None - for proposedEncoding in overrideEncodings: - u = self._convertFrom(proposedEncoding) - if u: break - if not u: - for proposedEncoding in (documentEncoding, sniffedEncoding): - u = self._convertFrom(proposedEncoding) - if u: break - - # If no luck and we have auto-detection library, try that: - if not u and chardet and not isinstance(self.markup, unicode): - u = self._convertFrom(chardet.detect(self.markup)['encoding']) - - # As a last resort, try utf-8 and windows-1252: - if not u: - for proposed_encoding in ("utf-8", "windows-1252"): - u = self._convertFrom(proposed_encoding) - if u: break - - self.unicode = u - if not u: self.originalEncoding = None - - def _subMSChar(self, match): - """Changes a MS smart quote character to an XML or HTML - entity.""" - orig = match.group(1) - sub = self.MS_CHARS.get(orig) - if type(sub) == types.TupleType: - if self.smartQuotesTo == 'xml': - sub = '&#x'.encode() + sub[1].encode() + ';'.encode() - else: - sub = '&'.encode() + sub[0].encode() + ';'.encode() - else: - sub = sub.encode() - return sub - - def _convertFrom(self, proposed): - proposed = self.find_codec(proposed) - if not proposed or proposed in self.triedEncodings: - return None - self.triedEncodings.append(proposed) - markup = self.markup - - # Convert smart quotes to HTML if coming from an encoding - # that might have them. - if self.smartQuotesTo and proposed.lower() in("windows-1252", - "iso-8859-1", - "iso-8859-2"): - smart_quotes_re = "([\x80-\x9f])" - smart_quotes_compiled = re.compile(smart_quotes_re) - markup = smart_quotes_compiled.sub(self._subMSChar, markup) - - try: - # print "Trying to convert document to %s" % proposed - u = self._toUnicode(markup, proposed) - self.markup = u - self.originalEncoding = proposed - except Exception, e: - # print "That didn't work!" - # print e - return None - #print "Correct encoding: %s" % proposed - return self.markup - - def _toUnicode(self, data, encoding): - '''Given a string and its encoding, decodes the string into Unicode. - %encoding is a string recognized by encodings.aliases''' - - # strip Byte Order Mark (if present) - if (len(data) >= 4) and (data[:2] == '\xfe\xff') \ - and (data[2:4] != '\x00\x00'): - encoding = 'utf-16be' - data = data[2:] - elif (len(data) >= 4) and (data[:2] == '\xff\xfe') \ - and (data[2:4] != '\x00\x00'): - encoding = 'utf-16le' - data = data[2:] - elif data[:3] == '\xef\xbb\xbf': - encoding = 'utf-8' - data = data[3:] - elif data[:4] == '\x00\x00\xfe\xff': - encoding = 'utf-32be' - data = data[4:] - elif data[:4] == '\xff\xfe\x00\x00': - encoding = 'utf-32le' - data = data[4:] - newdata = unicode(data, encoding) - return newdata - - def _detectEncoding(self, xml_data, isHTML=False): - """Given a document, tries to detect its XML encoding.""" - xml_encoding = sniffed_xml_encoding = None - try: - if xml_data[:4] == '\x4c\x6f\xa7\x94': - # EBCDIC - xml_data = self._ebcdic_to_ascii(xml_data) - elif xml_data[:4] == '\x00\x3c\x00\x3f': - # UTF-16BE - sniffed_xml_encoding = 'utf-16be' - xml_data = unicode(xml_data, 'utf-16be').encode('utf-8') - elif (len(xml_data) >= 4) and (xml_data[:2] == '\xfe\xff') \ - and (xml_data[2:4] != '\x00\x00'): - # UTF-16BE with BOM - sniffed_xml_encoding = 'utf-16be' - xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8') - elif xml_data[:4] == '\x3c\x00\x3f\x00': - # UTF-16LE - sniffed_xml_encoding = 'utf-16le' - xml_data = unicode(xml_data, 'utf-16le').encode('utf-8') - elif (len(xml_data) >= 4) and (xml_data[:2] == '\xff\xfe') and \ - (xml_data[2:4] != '\x00\x00'): - # UTF-16LE with BOM - sniffed_xml_encoding = 'utf-16le' - xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8') - elif xml_data[:4] == '\x00\x00\x00\x3c': - # UTF-32BE - sniffed_xml_encoding = 'utf-32be' - xml_data = unicode(xml_data, 'utf-32be').encode('utf-8') - elif xml_data[:4] == '\x3c\x00\x00\x00': - # UTF-32LE - sniffed_xml_encoding = 'utf-32le' - xml_data = unicode(xml_data, 'utf-32le').encode('utf-8') - elif xml_data[:4] == '\x00\x00\xfe\xff': - # UTF-32BE with BOM - sniffed_xml_encoding = 'utf-32be' - xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8') - elif xml_data[:4] == '\xff\xfe\x00\x00': - # UTF-32LE with BOM - sniffed_xml_encoding = 'utf-32le' - xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8') - elif xml_data[:3] == '\xef\xbb\xbf': - # UTF-8 with BOM - sniffed_xml_encoding = 'utf-8' - xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8') - else: - sniffed_xml_encoding = 'ascii' - pass - except: - xml_encoding_match = None - xml_encoding_re = '^<\?.*encoding=[\'"](.*?)[\'"].*\?>'.encode() - xml_encoding_match = re.compile(xml_encoding_re).match(xml_data) - if not xml_encoding_match and isHTML: - meta_re = '<\s*meta[^>]+charset=([^>]*?)[;\'">]'.encode() - regexp = re.compile(meta_re, re.I) - xml_encoding_match = regexp.search(xml_data) - if xml_encoding_match is not None: - xml_encoding = xml_encoding_match.groups()[0].decode( - 'ascii').lower() - if isHTML: - self.declaredHTMLEncoding = xml_encoding - if sniffed_xml_encoding and \ - (xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode', - 'iso-10646-ucs-4', 'ucs-4', 'csucs4', - 'utf-16', 'utf-32', 'utf_16', 'utf_32', - 'utf16', 'u16')): - xml_encoding = sniffed_xml_encoding - return xml_data, xml_encoding, sniffed_xml_encoding - - - def find_codec(self, charset): - return self._codec(self.CHARSET_ALIASES.get(charset, charset)) \ - or (charset and self._codec(charset.replace("-", ""))) \ - or (charset and self._codec(charset.replace("-", "_"))) \ - or charset - - def _codec(self, charset): - if not charset: return charset - codec = None - try: - codecs.lookup(charset) - codec = charset - except (LookupError, ValueError): - pass - return codec - - EBCDIC_TO_ASCII_MAP = None - def _ebcdic_to_ascii(self, s): - c = self.__class__ - if not c.EBCDIC_TO_ASCII_MAP: - emap = (0,1,2,3,156,9,134,127,151,141,142,11,12,13,14,15, - 16,17,18,19,157,133,8,135,24,25,146,143,28,29,30,31, - 128,129,130,131,132,10,23,27,136,137,138,139,140,5,6,7, - 144,145,22,147,148,149,150,4,152,153,154,155,20,21,158,26, - 32,160,161,162,163,164,165,166,167,168,91,46,60,40,43,33, - 38,169,170,171,172,173,174,175,176,177,93,36,42,41,59,94, - 45,47,178,179,180,181,182,183,184,185,124,44,37,95,62,63, - 186,187,188,189,190,191,192,193,194,96,58,35,64,39,61,34, - 195,97,98,99,100,101,102,103,104,105,196,197,198,199,200, - 201,202,106,107,108,109,110,111,112,113,114,203,204,205, - 206,207,208,209,126,115,116,117,118,119,120,121,122,210, - 211,212,213,214,215,216,217,218,219,220,221,222,223,224, - 225,226,227,228,229,230,231,123,65,66,67,68,69,70,71,72, - 73,232,233,234,235,236,237,125,74,75,76,77,78,79,80,81, - 82,238,239,240,241,242,243,92,159,83,84,85,86,87,88,89, - 90,244,245,246,247,248,249,48,49,50,51,52,53,54,55,56,57, - 250,251,252,253,254,255) - import string - c.EBCDIC_TO_ASCII_MAP = string.maketrans( \ - ''.join(map(chr, range(256))), ''.join(map(chr, emap))) - return s.translate(c.EBCDIC_TO_ASCII_MAP) - - MS_CHARS = { '\x80' : ('euro', '20AC'), - '\x81' : ' ', - '\x82' : ('sbquo', '201A'), - '\x83' : ('fnof', '192'), - '\x84' : ('bdquo', '201E'), - '\x85' : ('hellip', '2026'), - '\x86' : ('dagger', '2020'), - '\x87' : ('Dagger', '2021'), - '\x88' : ('circ', '2C6'), - '\x89' : ('permil', '2030'), - '\x8A' : ('Scaron', '160'), - '\x8B' : ('lsaquo', '2039'), - '\x8C' : ('OElig', '152'), - '\x8D' : '?', - '\x8E' : ('#x17D', '17D'), - '\x8F' : '?', - '\x90' : '?', - '\x91' : ('lsquo', '2018'), - '\x92' : ('rsquo', '2019'), - '\x93' : ('ldquo', '201C'), - '\x94' : ('rdquo', '201D'), - '\x95' : ('bull', '2022'), - '\x96' : ('ndash', '2013'), - '\x97' : ('mdash', '2014'), - '\x98' : ('tilde', '2DC'), - '\x99' : ('trade', '2122'), - '\x9a' : ('scaron', '161'), - '\x9b' : ('rsaquo', '203A'), - '\x9c' : ('oelig', '153'), - '\x9d' : '?', - '\x9e' : ('#x17E', '17E'), - '\x9f' : ('Yuml', ''),} - -####################################################################### - - -#By default, act as an HTML pretty-printer. -if __name__ == '__main__': - import sys - soup = BeautifulSoup(sys.stdin) - print soup.prettify() diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py b/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py deleted file mode 100644 index c2249c2..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py +++ /dev/null @@ -1,98 +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. - -# This module is required for Python to treat this directory as a package. - -"""Autoinstalls third-party code required by WebKit.""" - -from __future__ import with_statement - -import codecs -import os - -from webkitpy.common.system.autoinstall import AutoInstaller - -# Putting the autoinstall code into webkitpy/thirdparty/__init__.py -# ensures that no autoinstalling occurs until a caller imports from -# webkitpy.thirdparty. This is useful if the caller wants to configure -# logging prior to executing autoinstall code. - -# FIXME: Ideally, a package should be autoinstalled only if the caller -# attempts to import from that individual package. This would -# make autoinstalling lazier than it is currently. This can -# perhaps be done using Python's import hooks as the original -# autoinstall implementation did. - -# FIXME: If any of these servers is offline, webkit-patch breaks (and maybe -# other scripts do, too). See <http://webkit.org/b/42080>. - -# We put auto-installed third-party modules in this directory-- -# -# webkitpy/thirdparty/autoinstalled -thirdparty_dir = os.path.dirname(__file__) -autoinstalled_dir = os.path.join(thirdparty_dir, "autoinstalled") - -# We need to download ClientForm since the mechanize package that we download -# below requires it. The mechanize package uses ClientForm, for example, -# in _html.py. Since mechanize imports ClientForm in the following way, -# -# > import sgmllib, ClientForm -# -# the search path needs to include ClientForm. We put ClientForm in -# its own directory so that we can include it in the search path without -# including other modules as a side effect. -clientform_dir = os.path.join(autoinstalled_dir, "clientform") -installer = AutoInstaller(append_to_search_path=True, - target_dir=clientform_dir) -installer.install(url="http://pypi.python.org/packages/source/C/ClientForm/ClientForm-0.2.10.zip", - url_subpath="ClientForm.py") - -# The remaining packages do not need to be in the search path, so we create -# a new AutoInstaller instance that does not append to the search path. -installer = AutoInstaller(target_dir=autoinstalled_dir) - -installer.install(url="http://pypi.python.org/packages/source/m/mechanize/mechanize-0.1.11.zip", - url_subpath="mechanize") -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://www.adambarth.com/webkit/eliza", - target_name="eliza.py") - -# Since irclib and ircbot are two top-level packages, we need to import -# them separately. We group them into an irc package for better -# organization purposes. -irc_dir = os.path.join(autoinstalled_dir, "irc") -installer = AutoInstaller(target_dir=irc_dir) -installer.install(url="http://downloads.sourceforge.net/project/python-irclib/python-irclib/0.4.8/python-irclib-0.4.8.zip", url_subpath="irclib.py") -installer.install(url="http://downloads.sourceforge.net/project/python-irclib/python-irclib/0.4.8/python-irclib-0.4.8.zip", url_subpath="ircbot.py") - -pywebsocket_dir = os.path.join(autoinstalled_dir, "pywebsocket") -installer = AutoInstaller(target_dir=pywebsocket_dir) -installer.install(url="http://pywebsocket.googlecode.com/files/mod_pywebsocket-0.5.2.tar.gz", - url_subpath="pywebsocket-0.5.2/src/mod_pywebsocket") - -readme_path = os.path.join(autoinstalled_dir, "README") -if not os.path.exists(readme_path): - with codecs.open(readme_path, "w", "ascii") as file: - file.write("This directory is auto-generated by WebKit and is " - "safe to delete.\nIt contains needed third-party Python " - "packages automatically downloaded from the web.") diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/mock.py b/WebKitTools/Scripts/webkitpy/thirdparty/mock.py deleted file mode 100644 index 015c19e..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/mock.py +++ /dev/null @@ -1,309 +0,0 @@ -# mock.py -# Test tools for mocking and patching. -# Copyright (C) 2007-2009 Michael Foord -# E-mail: fuzzyman AT voidspace DOT org DOT uk - -# mock 0.6.0 -# http://www.voidspace.org.uk/python/mock/ - -# Released subject to the BSD License -# Please see http://www.voidspace.org.uk/python/license.shtml - -# 2009-11-25: Licence downloaded from above URL. -# BEGIN DOWNLOADED LICENSE -# -# Copyright (c) 2003-2009, Michael Foord -# All rights reserved. -# E-mail : fuzzyman AT voidspace DOT org DOT uk -# -# 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 Michael Foord nor the name of Voidspace -# 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. -# -# END DOWNLOADED LICENSE - -# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml -# Comments, suggestions and bug reports welcome. - - -__all__ = ( - 'Mock', - 'patch', - 'patch_object', - 'sentinel', - 'DEFAULT' -) - -__version__ = '0.6.0' - -class SentinelObject(object): - def __init__(self, name): - self.name = name - - def __repr__(self): - return '<SentinelObject "%s">' % self.name - - -class Sentinel(object): - def __init__(self): - self._sentinels = {} - - def __getattr__(self, name): - return self._sentinels.setdefault(name, SentinelObject(name)) - - -sentinel = Sentinel() - -DEFAULT = sentinel.DEFAULT - -class OldStyleClass: - pass -ClassType = type(OldStyleClass) - -def _is_magic(name): - return '__%s__' % name[2:-2] == name - -def _copy(value): - if type(value) in (dict, list, tuple, set): - return type(value)(value) - return value - - -class Mock(object): - - def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, - name=None, parent=None, wraps=None): - self._parent = parent - self._name = name - if spec is not None and not isinstance(spec, list): - spec = [member for member in dir(spec) if not _is_magic(member)] - - self._methods = spec - self._children = {} - self._return_value = return_value - self.side_effect = side_effect - self._wraps = wraps - - self.reset_mock() - - - def reset_mock(self): - self.called = False - self.call_args = None - self.call_count = 0 - self.call_args_list = [] - self.method_calls = [] - for child in self._children.itervalues(): - child.reset_mock() - if isinstance(self._return_value, Mock): - self._return_value.reset_mock() - - - def __get_return_value(self): - if self._return_value is DEFAULT: - self._return_value = Mock() - return self._return_value - - def __set_return_value(self, value): - self._return_value = value - - return_value = property(__get_return_value, __set_return_value) - - - def __call__(self, *args, **kwargs): - self.called = True - self.call_count += 1 - self.call_args = (args, kwargs) - self.call_args_list.append((args, kwargs)) - - parent = self._parent - name = self._name - while parent is not None: - parent.method_calls.append((name, args, kwargs)) - if parent._parent is None: - break - name = parent._name + '.' + name - parent = parent._parent - - ret_val = DEFAULT - if self.side_effect is not None: - if (isinstance(self.side_effect, Exception) or - isinstance(self.side_effect, (type, ClassType)) and - issubclass(self.side_effect, Exception)): - raise self.side_effect - - ret_val = self.side_effect(*args, **kwargs) - if ret_val is DEFAULT: - ret_val = self.return_value - - if self._wraps is not None and self._return_value is DEFAULT: - return self._wraps(*args, **kwargs) - if ret_val is DEFAULT: - ret_val = self.return_value - return ret_val - - - def __getattr__(self, name): - if self._methods is not None: - if name not in self._methods: - raise AttributeError("Mock object has no attribute '%s'" % name) - elif _is_magic(name): - raise AttributeError(name) - - if name not in self._children: - wraps = None - if self._wraps is not None: - wraps = getattr(self._wraps, name) - self._children[name] = Mock(parent=self, name=name, wraps=wraps) - - return self._children[name] - - - def assert_called_with(self, *args, **kwargs): - assert self.call_args == (args, kwargs), 'Expected: %s\nCalled with: %s' % ((args, kwargs), self.call_args) - - -def _dot_lookup(thing, comp, import_path): - try: - return getattr(thing, comp) - except AttributeError: - __import__(import_path) - return getattr(thing, comp) - - -def _importer(target): - components = target.split('.') - import_path = components.pop(0) - thing = __import__(import_path) - - for comp in components: - import_path += ".%s" % comp - thing = _dot_lookup(thing, comp, import_path) - return thing - - -class _patch(object): - def __init__(self, target, attribute, new, spec, create): - self.target = target - self.attribute = attribute - self.new = new - self.spec = spec - self.create = create - self.has_local = False - - - def __call__(self, func): - if hasattr(func, 'patchings'): - func.patchings.append(self) - return func - - def patched(*args, **keywargs): - # don't use a with here (backwards compatability with 2.5) - extra_args = [] - for patching in patched.patchings: - arg = patching.__enter__() - if patching.new is DEFAULT: - extra_args.append(arg) - args += tuple(extra_args) - try: - return func(*args, **keywargs) - finally: - for patching in getattr(patched, 'patchings', []): - patching.__exit__() - - patched.patchings = [self] - patched.__name__ = func.__name__ - patched.compat_co_firstlineno = getattr(func, "compat_co_firstlineno", - func.func_code.co_firstlineno) - return patched - - - def get_original(self): - target = self.target - name = self.attribute - create = self.create - - original = DEFAULT - if _has_local_attr(target, name): - try: - original = target.__dict__[name] - except AttributeError: - # for instances of classes with slots, they have no __dict__ - original = getattr(target, name) - elif not create and not hasattr(target, name): - raise AttributeError("%s does not have the attribute %r" % (target, name)) - return original - - - def __enter__(self): - new, spec, = self.new, self.spec - original = self.get_original() - if new is DEFAULT: - # XXXX what if original is DEFAULT - shouldn't use it as a spec - inherit = False - if spec == True: - # set spec to the object we are replacing - spec = original - if isinstance(spec, (type, ClassType)): - inherit = True - new = Mock(spec=spec) - if inherit: - new.return_value = Mock(spec=spec) - self.temp_original = original - setattr(self.target, self.attribute, new) - return new - - - def __exit__(self, *_): - if self.temp_original is not DEFAULT: - setattr(self.target, self.attribute, self.temp_original) - else: - delattr(self.target, self.attribute) - del self.temp_original - - -def patch_object(target, attribute, new=DEFAULT, spec=None, create=False): - return _patch(target, attribute, new, spec, create) - - -def patch(target, new=DEFAULT, spec=None, create=False): - try: - target, attribute = target.rsplit('.', 1) - except (TypeError, ValueError): - raise TypeError("Need a valid target to patch. You supplied: %r" % (target,)) - target = _importer(target) - return _patch(target, attribute, new, spec, create) - - - -def _has_local_attr(obj, name): - try: - return name in vars(obj) - except TypeError: - # objects without a __dict__ - return hasattr(obj, name) diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/LICENSE.txt b/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/LICENSE.txt deleted file mode 100644 index ad95f29..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2006 Bob Ippolito - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/README.txt b/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/README.txt deleted file mode 100644 index 7f726ce..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/README.txt +++ /dev/null @@ -1,11 +0,0 @@ -URL: http://undefined.org/python/#simplejson -Version: 1.7.3 -License: MIT -License File: LICENSE.txt - -Description: -simplejson is a JSON encoder and decoder for Python. - - -Local Modifications: -Removed unit tests from current distribution. diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/__init__.py b/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/__init__.py deleted file mode 100644 index 38d6229..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/__init__.py +++ /dev/null @@ -1,287 +0,0 @@ -r""" -A simple, fast, extensible JSON encoder and decoder - -JSON (JavaScript Object Notation) <http://json.org> is a subset of -JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data -interchange format. - -simplejson exposes an API familiar to uses of the standard library -marshal and pickle modules. - -Encoding basic Python object hierarchies:: - - >>> import simplejson - >>> simplejson.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) - '["foo", {"bar": ["baz", null, 1.0, 2]}]' - >>> print simplejson.dumps("\"foo\bar") - "\"foo\bar" - >>> print simplejson.dumps(u'\u1234') - "\u1234" - >>> print simplejson.dumps('\\') - "\\" - >>> print simplejson.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) - {"a": 0, "b": 0, "c": 0} - >>> from StringIO import StringIO - >>> io = StringIO() - >>> simplejson.dump(['streaming API'], io) - >>> io.getvalue() - '["streaming API"]' - -Compact encoding:: - - >>> import simplejson - >>> simplejson.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':')) - '[1,2,3,{"4":5,"6":7}]' - -Pretty printing:: - - >>> import simplejson - >>> print simplejson.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4) - { - "4": 5, - "6": 7 - } - -Decoding JSON:: - - >>> import simplejson - >>> simplejson.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') - [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] - >>> simplejson.loads('"\\"foo\\bar"') - u'"foo\x08ar' - >>> from StringIO import StringIO - >>> io = StringIO('["streaming API"]') - >>> simplejson.load(io) - [u'streaming API'] - -Specializing JSON object decoding:: - - >>> import simplejson - >>> def as_complex(dct): - ... if '__complex__' in dct: - ... return complex(dct['real'], dct['imag']) - ... return dct - ... - >>> simplejson.loads('{"__complex__": true, "real": 1, "imag": 2}', - ... object_hook=as_complex) - (1+2j) - -Extending JSONEncoder:: - - >>> import simplejson - >>> class ComplexEncoder(simplejson.JSONEncoder): - ... def default(self, obj): - ... if isinstance(obj, complex): - ... return [obj.real, obj.imag] - ... return simplejson.JSONEncoder.default(self, obj) - ... - >>> dumps(2 + 1j, cls=ComplexEncoder) - '[2.0, 1.0]' - >>> ComplexEncoder().encode(2 + 1j) - '[2.0, 1.0]' - >>> list(ComplexEncoder().iterencode(2 + 1j)) - ['[', '2.0', ', ', '1.0', ']'] - - -Note that the JSON produced by this module's default settings -is a subset of YAML, so it may be used as a serializer for that as well. -""" -__version__ = '1.7.3' -__all__ = [ - 'dump', 'dumps', 'load', 'loads', - 'JSONDecoder', 'JSONEncoder', -] - -from decoder import JSONDecoder -from encoder import JSONEncoder - -_default_encoder = JSONEncoder( - skipkeys=False, - ensure_ascii=True, - check_circular=True, - allow_nan=True, - indent=None, - separators=None, - encoding='utf-8' -) - -def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, - allow_nan=True, cls=None, indent=None, separators=None, - encoding='utf-8', **kw): - """ - Serialize ``obj`` as a JSON formatted stream to ``fp`` (a - ``.write()``-supporting file-like object). - - If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types - (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) - will be skipped instead of raising a ``TypeError``. - - If ``ensure_ascii`` is ``False``, then the some chunks written to ``fp`` - may be ``unicode`` instances, subject to normal Python ``str`` to - ``unicode`` coercion rules. Unless ``fp.write()`` explicitly - understands ``unicode`` (as in ``codecs.getwriter()``) this is likely - to cause an error. - - If ``check_circular`` is ``False``, then the circular reference check - for container types will be skipped and a circular reference will - result in an ``OverflowError`` (or worse). - - If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to - serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) - in strict compliance of the JSON specification, instead of using the - JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). - - If ``indent`` is a non-negative integer, then JSON array elements and object - members will be pretty-printed with that indent level. An indent level - of 0 will only insert newlines. ``None`` is the most compact representation. - - If ``separators`` is an ``(item_separator, dict_separator)`` tuple - then it will be used instead of the default ``(', ', ': ')`` separators. - ``(',', ':')`` is the most compact JSON representation. - - ``encoding`` is the character encoding for str instances, default is UTF-8. - - To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the - ``.default()`` method to serialize additional types), specify it with - the ``cls`` kwarg. - """ - # cached encoder - if (skipkeys is False and ensure_ascii is True and - check_circular is True and allow_nan is True and - cls is None and indent is None and separators is None and - encoding == 'utf-8' and not kw): - iterable = _default_encoder.iterencode(obj) - else: - if cls is None: - cls = JSONEncoder - iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, - check_circular=check_circular, allow_nan=allow_nan, indent=indent, - separators=separators, encoding=encoding, **kw).iterencode(obj) - # could accelerate with writelines in some versions of Python, at - # a debuggability cost - for chunk in iterable: - fp.write(chunk) - - -def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, - allow_nan=True, cls=None, indent=None, separators=None, - encoding='utf-8', **kw): - """ - Serialize ``obj`` to a JSON formatted ``str``. - - If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types - (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) - will be skipped instead of raising a ``TypeError``. - - If ``ensure_ascii`` is ``False``, then the return value will be a - ``unicode`` instance subject to normal Python ``str`` to ``unicode`` - coercion rules instead of being escaped to an ASCII ``str``. - - If ``check_circular`` is ``False``, then the circular reference check - for container types will be skipped and a circular reference will - result in an ``OverflowError`` (or worse). - - If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to - serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in - strict compliance of the JSON specification, instead of using the - JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). - - If ``indent`` is a non-negative integer, then JSON array elements and - object members will be pretty-printed with that indent level. An indent - level of 0 will only insert newlines. ``None`` is the most compact - representation. - - If ``separators`` is an ``(item_separator, dict_separator)`` tuple - then it will be used instead of the default ``(', ', ': ')`` separators. - ``(',', ':')`` is the most compact JSON representation. - - ``encoding`` is the character encoding for str instances, default is UTF-8. - - To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the - ``.default()`` method to serialize additional types), specify it with - the ``cls`` kwarg. - """ - # cached encoder - if (skipkeys is False and ensure_ascii is True and - check_circular is True and allow_nan is True and - cls is None and indent is None and separators is None and - encoding == 'utf-8' and not kw): - return _default_encoder.encode(obj) - if cls is None: - cls = JSONEncoder - return cls( - skipkeys=skipkeys, ensure_ascii=ensure_ascii, - check_circular=check_circular, allow_nan=allow_nan, indent=indent, - separators=separators, encoding=encoding, - **kw).encode(obj) - -_default_decoder = JSONDecoder(encoding=None, object_hook=None) - -def load(fp, encoding=None, cls=None, object_hook=None, **kw): - """ - Deserialize ``fp`` (a ``.read()``-supporting file-like object containing - a JSON document) to a Python object. - - If the contents of ``fp`` is encoded with an ASCII based encoding other - than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must - be specified. Encodings that are not ASCII based (such as UCS-2) are - not allowed, and should be wrapped with - ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode`` - object and passed to ``loads()`` - - ``object_hook`` is an optional function that will be called with the - result of any object literal decode (a ``dict``). The return value of - ``object_hook`` will be used instead of the ``dict``. This feature - can be used to implement custom decoders (e.g. JSON-RPC class hinting). - - To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` - kwarg. - """ - return loads(fp.read(), - encoding=encoding, cls=cls, object_hook=object_hook, **kw) - -def loads(s, encoding=None, cls=None, object_hook=None, **kw): - """ - Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON - document) to a Python object. - - If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding - other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name - must be specified. Encodings that are not ASCII based (such as UCS-2) - are not allowed and should be decoded to ``unicode`` first. - - ``object_hook`` is an optional function that will be called with the - result of any object literal decode (a ``dict``). The return value of - ``object_hook`` will be used instead of the ``dict``. This feature - can be used to implement custom decoders (e.g. JSON-RPC class hinting). - - To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` - kwarg. - """ - if cls is None and encoding is None and object_hook is None and not kw: - return _default_decoder.decode(s) - if cls is None: - cls = JSONDecoder - if object_hook is not None: - kw['object_hook'] = object_hook - return cls(encoding=encoding, **kw).decode(s) - -def read(s): - """ - json-py API compatibility hook. Use loads(s) instead. - """ - import warnings - warnings.warn("simplejson.loads(s) should be used instead of read(s)", - DeprecationWarning) - return loads(s) - -def write(obj): - """ - json-py API compatibility hook. Use dumps(s) instead. - """ - import warnings - warnings.warn("simplejson.dumps(s) should be used instead of write(s)", - DeprecationWarning) - return dumps(obj) - - diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/_speedups.c b/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/_speedups.c deleted file mode 100644 index 8f290bb..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/_speedups.c +++ /dev/null @@ -1,215 +0,0 @@ -#include "Python.h" -#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) -typedef int Py_ssize_t; -#define PY_SSIZE_T_MAX INT_MAX -#define PY_SSIZE_T_MIN INT_MIN -#endif - -static Py_ssize_t -ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars); -static PyObject * -ascii_escape_unicode(PyObject *pystr); -static PyObject * -ascii_escape_str(PyObject *pystr); -static PyObject * -py_encode_basestring_ascii(PyObject* self __attribute__((__unused__)), PyObject *pystr); -void init_speedups(void); - -#define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '/' && c != '"') - -#define MIN_EXPANSION 6 -#ifdef Py_UNICODE_WIDE -#define MAX_EXPANSION (2 * MIN_EXPANSION) -#else -#define MAX_EXPANSION MIN_EXPANSION -#endif - -static Py_ssize_t -ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars) { - Py_UNICODE x; - output[chars++] = '\\'; - switch (c) { - case '/': output[chars++] = (char)c; break; - case '\\': output[chars++] = (char)c; break; - case '"': output[chars++] = (char)c; break; - case '\b': output[chars++] = 'b'; break; - case '\f': output[chars++] = 'f'; break; - case '\n': output[chars++] = 'n'; break; - case '\r': output[chars++] = 'r'; break; - case '\t': output[chars++] = 't'; break; - default: -#ifdef Py_UNICODE_WIDE - if (c >= 0x10000) { - /* UTF-16 surrogate pair */ - Py_UNICODE v = c - 0x10000; - c = 0xd800 | ((v >> 10) & 0x3ff); - output[chars++] = 'u'; - x = (c & 0xf000) >> 12; - output[chars++] = (x < 10) ? '0' + x : 'a' + (x - 10); - x = (c & 0x0f00) >> 8; - output[chars++] = (x < 10) ? '0' + x : 'a' + (x - 10); - x = (c & 0x00f0) >> 4; - output[chars++] = (x < 10) ? '0' + x : 'a' + (x - 10); - x = (c & 0x000f); - output[chars++] = (x < 10) ? '0' + x : 'a' + (x - 10); - c = 0xdc00 | (v & 0x3ff); - output[chars++] = '\\'; - } -#endif - output[chars++] = 'u'; - x = (c & 0xf000) >> 12; - output[chars++] = (x < 10) ? '0' + x : 'a' + (x - 10); - x = (c & 0x0f00) >> 8; - output[chars++] = (x < 10) ? '0' + x : 'a' + (x - 10); - x = (c & 0x00f0) >> 4; - output[chars++] = (x < 10) ? '0' + x : 'a' + (x - 10); - x = (c & 0x000f); - output[chars++] = (x < 10) ? '0' + x : 'a' + (x - 10); - } - return chars; -} - -static PyObject * -ascii_escape_unicode(PyObject *pystr) { - Py_ssize_t i; - Py_ssize_t input_chars; - Py_ssize_t output_size; - Py_ssize_t chars; - PyObject *rval; - char *output; - Py_UNICODE *input_unicode; - - input_chars = PyUnicode_GET_SIZE(pystr); - input_unicode = PyUnicode_AS_UNICODE(pystr); - /* One char input can be up to 6 chars output, estimate 4 of these */ - output_size = 2 + (MIN_EXPANSION * 4) + input_chars; - rval = PyString_FromStringAndSize(NULL, output_size); - if (rval == NULL) { - return NULL; - } - output = PyString_AS_STRING(rval); - chars = 0; - output[chars++] = '"'; - for (i = 0; i < input_chars; i++) { - Py_UNICODE c = input_unicode[i]; - if (S_CHAR(c)) { - output[chars++] = (char)c; - } else { - chars = ascii_escape_char(c, output, chars); - } - if (output_size - chars < (1 + MAX_EXPANSION)) { - /* There's more than four, so let's resize by a lot */ - output_size *= 2; - /* This is an upper bound */ - if (output_size > 2 + (input_chars * MAX_EXPANSION)) { - output_size = 2 + (input_chars * MAX_EXPANSION); - } - if (_PyString_Resize(&rval, output_size) == -1) { - return NULL; - } - output = PyString_AS_STRING(rval); - } - } - output[chars++] = '"'; - if (_PyString_Resize(&rval, chars) == -1) { - return NULL; - } - return rval; -} - -static PyObject * -ascii_escape_str(PyObject *pystr) { - Py_ssize_t i; - Py_ssize_t input_chars; - Py_ssize_t output_size; - Py_ssize_t chars; - PyObject *rval; - char *output; - char *input_str; - - input_chars = PyString_GET_SIZE(pystr); - input_str = PyString_AS_STRING(pystr); - /* One char input can be up to 6 chars output, estimate 4 of these */ - output_size = 2 + (MIN_EXPANSION * 4) + input_chars; - rval = PyString_FromStringAndSize(NULL, output_size); - if (rval == NULL) { - return NULL; - } - output = PyString_AS_STRING(rval); - chars = 0; - output[chars++] = '"'; - for (i = 0; i < input_chars; i++) { - Py_UNICODE c = (Py_UNICODE)input_str[i]; - if (S_CHAR(c)) { - output[chars++] = (char)c; - } else if (c > 0x7F) { - /* We hit a non-ASCII character, bail to unicode mode */ - PyObject *uni; - Py_DECREF(rval); - uni = PyUnicode_DecodeUTF8(input_str, input_chars, "strict"); - if (uni == NULL) { - return NULL; - } - rval = ascii_escape_unicode(uni); - Py_DECREF(uni); - return rval; - } else { - chars = ascii_escape_char(c, output, chars); - } - /* An ASCII char can't possibly expand to a surrogate! */ - if (output_size - chars < (1 + MIN_EXPANSION)) { - /* There's more than four, so let's resize by a lot */ - output_size *= 2; - if (output_size > 2 + (input_chars * MIN_EXPANSION)) { - output_size = 2 + (input_chars * MIN_EXPANSION); - } - if (_PyString_Resize(&rval, output_size) == -1) { - return NULL; - } - output = PyString_AS_STRING(rval); - } - } - output[chars++] = '"'; - if (_PyString_Resize(&rval, chars) == -1) { - return NULL; - } - return rval; -} - -PyDoc_STRVAR(pydoc_encode_basestring_ascii, - "encode_basestring_ascii(basestring) -> str\n" - "\n" - "..." -); - -static PyObject * -py_encode_basestring_ascii(PyObject* self __attribute__((__unused__)), PyObject *pystr) { - /* METH_O */ - if (PyString_Check(pystr)) { - return ascii_escape_str(pystr); - } else if (PyUnicode_Check(pystr)) { - return ascii_escape_unicode(pystr); - } - PyErr_SetString(PyExc_TypeError, "first argument must be a string"); - return NULL; -} - -#define DEFN(n, k) \ - { \ - #n, \ - (PyCFunction)py_ ##n, \ - k, \ - pydoc_ ##n \ - } -static PyMethodDef speedups_methods[] = { - DEFN(encode_basestring_ascii, METH_O), - {} -}; -#undef DEFN - -void -init_speedups(void) -{ - PyObject *m; - m = Py_InitModule4("_speedups", speedups_methods, NULL, NULL, PYTHON_API_VERSION); -} diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/decoder.py b/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/decoder.py deleted file mode 100644 index 63f70cb..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/decoder.py +++ /dev/null @@ -1,273 +0,0 @@ -""" -Implementation of JSONDecoder -""" -import re - -from scanner import Scanner, pattern - -FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL - -def _floatconstants(): - import struct - import sys - _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') - if sys.byteorder != 'big': - _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] - nan, inf = struct.unpack('dd', _BYTES) - return nan, inf, -inf - -NaN, PosInf, NegInf = _floatconstants() - -def linecol(doc, pos): - lineno = doc.count('\n', 0, pos) + 1 - if lineno == 1: - colno = pos - else: - colno = pos - doc.rindex('\n', 0, pos) - return lineno, colno - -def errmsg(msg, doc, pos, end=None): - lineno, colno = linecol(doc, pos) - if end is None: - return '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos) - endlineno, endcolno = linecol(doc, end) - return '%s: line %d column %d - line %d column %d (char %d - %d)' % ( - msg, lineno, colno, endlineno, endcolno, pos, end) - -_CONSTANTS = { - '-Infinity': NegInf, - 'Infinity': PosInf, - 'NaN': NaN, - 'true': True, - 'false': False, - 'null': None, -} - -def JSONConstant(match, context, c=_CONSTANTS): - return c[match.group(0)], None -pattern('(-?Infinity|NaN|true|false|null)')(JSONConstant) - -def JSONNumber(match, context): - match = JSONNumber.regex.match(match.string, *match.span()) - integer, frac, exp = match.groups() - if frac or exp: - res = float(integer + (frac or '') + (exp or '')) - else: - res = int(integer) - return res, None -pattern(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?')(JSONNumber) - -STRINGCHUNK = re.compile(r'(.*?)(["\\])', FLAGS) -BACKSLASH = { - '"': u'"', '\\': u'\\', '/': u'/', - 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', -} - -DEFAULT_ENCODING = "utf-8" - -def scanstring(s, end, encoding=None, _b=BACKSLASH, _m=STRINGCHUNK.match): - if encoding is None: - encoding = DEFAULT_ENCODING - chunks = [] - _append = chunks.append - begin = end - 1 - while 1: - chunk = _m(s, end) - if chunk is None: - raise ValueError( - errmsg("Unterminated string starting at", s, begin)) - end = chunk.end() - content, terminator = chunk.groups() - if content: - if not isinstance(content, unicode): - content = unicode(content, encoding) - _append(content) - if terminator == '"': - break - try: - esc = s[end] - except IndexError: - raise ValueError( - errmsg("Unterminated string starting at", s, begin)) - if esc != 'u': - try: - m = _b[esc] - except KeyError: - raise ValueError( - errmsg("Invalid \\escape: %r" % (esc,), s, end)) - end += 1 - else: - esc = s[end + 1:end + 5] - try: - m = unichr(int(esc, 16)) - if len(esc) != 4 or not esc.isalnum(): - raise ValueError - except ValueError: - raise ValueError(errmsg("Invalid \\uXXXX escape", s, end)) - end += 5 - _append(m) - return u''.join(chunks), end - -def JSONString(match, context): - encoding = getattr(context, 'encoding', None) - return scanstring(match.string, match.end(), encoding) -pattern(r'"')(JSONString) - -WHITESPACE = re.compile(r'\s*', FLAGS) - -def JSONObject(match, context, _w=WHITESPACE.match): - pairs = {} - s = match.string - end = _w(s, match.end()).end() - nextchar = s[end:end + 1] - # trivial empty object - if nextchar == '}': - return pairs, end + 1 - if nextchar != '"': - raise ValueError(errmsg("Expecting property name", s, end)) - end += 1 - encoding = getattr(context, 'encoding', None) - iterscan = JSONScanner.iterscan - while True: - key, end = scanstring(s, end, encoding) - end = _w(s, end).end() - if s[end:end + 1] != ':': - raise ValueError(errmsg("Expecting : delimiter", s, end)) - end = _w(s, end + 1).end() - try: - value, end = iterscan(s, idx=end, context=context).next() - except StopIteration: - raise ValueError(errmsg("Expecting object", s, end)) - pairs[key] = value - end = _w(s, end).end() - nextchar = s[end:end + 1] - end += 1 - if nextchar == '}': - break - if nextchar != ',': - raise ValueError(errmsg("Expecting , delimiter", s, end - 1)) - end = _w(s, end).end() - nextchar = s[end:end + 1] - end += 1 - if nextchar != '"': - raise ValueError(errmsg("Expecting property name", s, end - 1)) - object_hook = getattr(context, 'object_hook', None) - if object_hook is not None: - pairs = object_hook(pairs) - return pairs, end -pattern(r'{')(JSONObject) - -def JSONArray(match, context, _w=WHITESPACE.match): - values = [] - s = match.string - end = _w(s, match.end()).end() - # look-ahead for trivial empty array - nextchar = s[end:end + 1] - if nextchar == ']': - return values, end + 1 - iterscan = JSONScanner.iterscan - while True: - try: - value, end = iterscan(s, idx=end, context=context).next() - except StopIteration: - raise ValueError(errmsg("Expecting object", s, end)) - values.append(value) - end = _w(s, end).end() - nextchar = s[end:end + 1] - end += 1 - if nextchar == ']': - break - if nextchar != ',': - raise ValueError(errmsg("Expecting , delimiter", s, end)) - end = _w(s, end).end() - return values, end -pattern(r'\[')(JSONArray) - -ANYTHING = [ - JSONObject, - JSONArray, - JSONString, - JSONConstant, - JSONNumber, -] - -JSONScanner = Scanner(ANYTHING) - -class JSONDecoder(object): - """ - Simple JSON <http://json.org> decoder - - Performs the following translations in decoding: - - +---------------+-------------------+ - | JSON | Python | - +===============+===================+ - | object | dict | - +---------------+-------------------+ - | array | list | - +---------------+-------------------+ - | string | unicode | - +---------------+-------------------+ - | number (int) | int, long | - +---------------+-------------------+ - | number (real) | float | - +---------------+-------------------+ - | true | True | - +---------------+-------------------+ - | false | False | - +---------------+-------------------+ - | null | None | - +---------------+-------------------+ - - It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as - their corresponding ``float`` values, which is outside the JSON spec. - """ - - _scanner = Scanner(ANYTHING) - __all__ = ['__init__', 'decode', 'raw_decode'] - - def __init__(self, encoding=None, object_hook=None): - """ - ``encoding`` determines the encoding used to interpret any ``str`` - objects decoded by this instance (utf-8 by default). It has no - effect when decoding ``unicode`` objects. - - Note that currently only encodings that are a superset of ASCII work, - strings of other encodings should be passed in as ``unicode``. - - ``object_hook``, if specified, will be called with the result - of every JSON object decoded and its return value will be used in - place of the given ``dict``. This can be used to provide custom - deserializations (e.g. to support JSON-RPC class hinting). - """ - self.encoding = encoding - self.object_hook = object_hook - - def decode(self, s, _w=WHITESPACE.match): - """ - Return the Python representation of ``s`` (a ``str`` or ``unicode`` - instance containing a JSON document) - """ - obj, end = self.raw_decode(s, idx=_w(s, 0).end()) - end = _w(s, end).end() - if end != len(s): - raise ValueError(errmsg("Extra data", s, end, len(s))) - return obj - - def raw_decode(self, s, **kw): - """ - Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning - with a JSON document) and return a 2-tuple of the Python - representation and the index in ``s`` where the document ended. - - This can be used to decode a JSON document from a string that may - have extraneous data at the end. - """ - kw.setdefault('context', self) - try: - obj, end = self._scanner.iterscan(s, **kw).next() - except StopIteration: - raise ValueError("No JSON object could be decoded") - return obj, end - -__all__ = ['JSONDecoder'] diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/encoder.py b/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/encoder.py deleted file mode 100644 index d29919a..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/encoder.py +++ /dev/null @@ -1,371 +0,0 @@ -""" -Implementation of JSONEncoder -""" -import re -try: - from simplejson import _speedups -except ImportError: - _speedups = None - -ESCAPE = re.compile(r'[\x00-\x19\\"\b\f\n\r\t]') -ESCAPE_ASCII = re.compile(r'([\\"/]|[^\ -~])') -ESCAPE_DCT = { - # escape all forward slashes to prevent </script> attack - '/': '\\/', - '\\': '\\\\', - '"': '\\"', - '\b': '\\b', - '\f': '\\f', - '\n': '\\n', - '\r': '\\r', - '\t': '\\t', -} -for i in range(0x20): - ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) - -# assume this produces an infinity on all machines (probably not guaranteed) -INFINITY = float('1e66666') - -def floatstr(o, allow_nan=True): - # Check for specials. Note that this type of test is processor- and/or - # platform-specific, so do tests which don't depend on the internals. - - if o != o: - text = 'NaN' - elif o == INFINITY: - text = 'Infinity' - elif o == -INFINITY: - text = '-Infinity' - else: - return repr(o) - - if not allow_nan: - raise ValueError("Out of range float values are not JSON compliant: %r" - % (o,)) - - return text - - -def encode_basestring(s): - """ - Return a JSON representation of a Python string - """ - def replace(match): - return ESCAPE_DCT[match.group(0)] - return '"' + ESCAPE.sub(replace, s) + '"' - -def encode_basestring_ascii(s): - def replace(match): - s = match.group(0) - try: - return ESCAPE_DCT[s] - except KeyError: - n = ord(s) - if n < 0x10000: - return '\\u%04x' % (n,) - else: - # surrogate pair - n -= 0x10000 - s1 = 0xd800 | ((n >> 10) & 0x3ff) - s2 = 0xdc00 | (n & 0x3ff) - return '\\u%04x\\u%04x' % (s1, s2) - return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' - -try: - encode_basestring_ascii = _speedups.encode_basestring_ascii - _need_utf8 = True -except AttributeError: - _need_utf8 = False - -class JSONEncoder(object): - """ - Extensible JSON <http://json.org> encoder for Python data structures. - - Supports the following objects and types by default: - - +-------------------+---------------+ - | Python | JSON | - +===================+===============+ - | dict | object | - +-------------------+---------------+ - | list, tuple | array | - +-------------------+---------------+ - | str, unicode | string | - +-------------------+---------------+ - | int, long, float | number | - +-------------------+---------------+ - | True | true | - +-------------------+---------------+ - | False | false | - +-------------------+---------------+ - | None | null | - +-------------------+---------------+ - - To extend this to recognize other objects, subclass and implement a - ``.default()`` method with another method that returns a serializable - object for ``o`` if possible, otherwise it should call the superclass - implementation (to raise ``TypeError``). - """ - __all__ = ['__init__', 'default', 'encode', 'iterencode'] - item_separator = ', ' - key_separator = ': ' - def __init__(self, skipkeys=False, ensure_ascii=True, - check_circular=True, allow_nan=True, sort_keys=False, - indent=None, separators=None, encoding='utf-8'): - """ - Constructor for JSONEncoder, with sensible defaults. - - If skipkeys is False, then it is a TypeError to attempt - encoding of keys that are not str, int, long, float or None. If - skipkeys is True, such items are simply skipped. - - If ensure_ascii is True, the output is guaranteed to be str - objects with all incoming unicode characters escaped. If - ensure_ascii is false, the output will be unicode object. - - If check_circular is True, then lists, dicts, and custom encoded - objects will be checked for circular references during encoding to - prevent an infinite recursion (which would cause an OverflowError). - Otherwise, no such check takes place. - - If allow_nan is True, then NaN, Infinity, and -Infinity will be - encoded as such. This behavior is not JSON specification compliant, - but is consistent with most JavaScript based encoders and decoders. - Otherwise, it will be a ValueError to encode such floats. - - If sort_keys is True, then the output of dictionaries will be - sorted by key; this is useful for regression tests to ensure - that JSON serializations can be compared on a day-to-day basis. - - If indent is a non-negative integer, then JSON array - elements and object members will be pretty-printed with that - indent level. An indent level of 0 will only insert newlines. - None is the most compact representation. - - If specified, separators should be a (item_separator, key_separator) - tuple. The default is (', ', ': '). To get the most compact JSON - representation you should specify (',', ':') to eliminate whitespace. - - If encoding is not None, then all input strings will be - transformed into unicode using that encoding prior to JSON-encoding. - The default is UTF-8. - """ - - self.skipkeys = skipkeys - self.ensure_ascii = ensure_ascii - self.check_circular = check_circular - self.allow_nan = allow_nan - self.sort_keys = sort_keys - self.indent = indent - self.current_indent_level = 0 - if separators is not None: - self.item_separator, self.key_separator = separators - self.encoding = encoding - - def _newline_indent(self): - return '\n' + (' ' * (self.indent * self.current_indent_level)) - - def _iterencode_list(self, lst, markers=None): - if not lst: - yield '[]' - return - if markers is not None: - markerid = id(lst) - if markerid in markers: - raise ValueError("Circular reference detected") - markers[markerid] = lst - yield '[' - if self.indent is not None: - self.current_indent_level += 1 - newline_indent = self._newline_indent() - separator = self.item_separator + newline_indent - yield newline_indent - else: - newline_indent = None - separator = self.item_separator - first = True - for value in lst: - if first: - first = False - else: - yield separator - for chunk in self._iterencode(value, markers): - yield chunk - if newline_indent is not None: - self.current_indent_level -= 1 - yield self._newline_indent() - yield ']' - if markers is not None: - del markers[markerid] - - def _iterencode_dict(self, dct, markers=None): - if not dct: - yield '{}' - return - if markers is not None: - markerid = id(dct) - if markerid in markers: - raise ValueError("Circular reference detected") - markers[markerid] = dct - yield '{' - key_separator = self.key_separator - if self.indent is not None: - self.current_indent_level += 1 - newline_indent = self._newline_indent() - item_separator = self.item_separator + newline_indent - yield newline_indent - else: - newline_indent = None - item_separator = self.item_separator - first = True - if self.ensure_ascii: - encoder = encode_basestring_ascii - else: - encoder = encode_basestring - allow_nan = self.allow_nan - if self.sort_keys: - keys = dct.keys() - keys.sort() - items = [(k, dct[k]) for k in keys] - else: - items = dct.iteritems() - _encoding = self.encoding - _do_decode = (_encoding is not None - and not (_need_utf8 and _encoding == 'utf-8')) - for key, value in items: - if isinstance(key, str): - if _do_decode: - key = key.decode(_encoding) - elif isinstance(key, basestring): - pass - # JavaScript is weakly typed for these, so it makes sense to - # also allow them. Many encoders seem to do something like this. - elif isinstance(key, float): - key = floatstr(key, allow_nan) - elif isinstance(key, (int, long)): - key = str(key) - elif key is True: - key = 'true' - elif key is False: - key = 'false' - elif key is None: - key = 'null' - elif self.skipkeys: - continue - else: - raise TypeError("key %r is not a string" % (key,)) - if first: - first = False - else: - yield item_separator - yield encoder(key) - yield key_separator - for chunk in self._iterencode(value, markers): - yield chunk - if newline_indent is not None: - self.current_indent_level -= 1 - yield self._newline_indent() - yield '}' - if markers is not None: - del markers[markerid] - - def _iterencode(self, o, markers=None): - if isinstance(o, basestring): - if self.ensure_ascii: - encoder = encode_basestring_ascii - else: - encoder = encode_basestring - _encoding = self.encoding - if (_encoding is not None and isinstance(o, str) - and not (_need_utf8 and _encoding == 'utf-8')): - o = o.decode(_encoding) - yield encoder(o) - elif o is None: - yield 'null' - elif o is True: - yield 'true' - elif o is False: - yield 'false' - elif isinstance(o, (int, long)): - yield str(o) - elif isinstance(o, float): - yield floatstr(o, self.allow_nan) - elif isinstance(o, (list, tuple)): - for chunk in self._iterencode_list(o, markers): - yield chunk - elif isinstance(o, dict): - for chunk in self._iterencode_dict(o, markers): - yield chunk - else: - if markers is not None: - markerid = id(o) - if markerid in markers: - raise ValueError("Circular reference detected") - markers[markerid] = o - for chunk in self._iterencode_default(o, markers): - yield chunk - if markers is not None: - del markers[markerid] - - def _iterencode_default(self, o, markers=None): - newobj = self.default(o) - return self._iterencode(newobj, markers) - - def default(self, o): - """ - Implement this method in a subclass such that it returns - a serializable object for ``o``, or calls the base implementation - (to raise a ``TypeError``). - - For example, to support arbitrary iterators, you could - implement default like this:: - - def default(self, o): - try: - iterable = iter(o) - except TypeError: - pass - else: - return list(iterable) - return JSONEncoder.default(self, o) - """ - raise TypeError("%r is not JSON serializable" % (o,)) - - def encode(self, o): - """ - Return a JSON string representation of a Python data structure. - - >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) - '{"foo":["bar", "baz"]}' - """ - # This is for extremely simple cases and benchmarks... - if isinstance(o, basestring): - if isinstance(o, str): - _encoding = self.encoding - if (_encoding is not None - and not (_encoding == 'utf-8' and _need_utf8)): - o = o.decode(_encoding) - return encode_basestring_ascii(o) - # This doesn't pass the iterator directly to ''.join() because it - # sucks at reporting exceptions. It's going to do this internally - # anyway because it uses PySequence_Fast or similar. - chunks = list(self.iterencode(o)) - return ''.join(chunks) - - def iterencode(self, o): - """ - Encode the given object and yield each string - representation as available. - - For example:: - - for chunk in JSONEncoder().iterencode(bigobject): - mysocket.write(chunk) - """ - if self.check_circular: - markers = {} - else: - markers = None - return self._iterencode(o, markers) - -__all__ = ['JSONEncoder'] diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/jsonfilter.py b/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/jsonfilter.py deleted file mode 100644 index 01ca21d..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/jsonfilter.py +++ /dev/null @@ -1,40 +0,0 @@ -import simplejson -import cgi - -class JSONFilter(object): - def __init__(self, app, mime_type='text/x-json'): - self.app = app - self.mime_type = mime_type - - def __call__(self, environ, start_response): - # Read JSON POST input to jsonfilter.json if matching mime type - response = {'status': '200 OK', 'headers': []} - def json_start_response(status, headers): - response['status'] = status - response['headers'].extend(headers) - environ['jsonfilter.mime_type'] = self.mime_type - if environ.get('REQUEST_METHOD', '') == 'POST': - if environ.get('CONTENT_TYPE', '') == self.mime_type: - args = [_ for _ in [environ.get('CONTENT_LENGTH')] if _] - data = environ['wsgi.input'].read(*map(int, args)) - environ['jsonfilter.json'] = simplejson.loads(data) - res = simplejson.dumps(self.app(environ, json_start_response)) - jsonp = cgi.parse_qs(environ.get('QUERY_STRING', '')).get('jsonp') - if jsonp: - content_type = 'text/javascript' - res = ''.join(jsonp + ['(', res, ')']) - elif 'Opera' in environ.get('HTTP_USER_AGENT', ''): - # Opera has bunk XMLHttpRequest support for most mime types - content_type = 'text/plain' - else: - content_type = self.mime_type - headers = [ - ('Content-type', content_type), - ('Content-length', len(res)), - ] - headers.extend(response['headers']) - start_response(response['status'], headers) - return [res] - -def factory(app, global_conf, **kw): - return JSONFilter(app, **kw) diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/scanner.py b/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/scanner.py deleted file mode 100644 index 64f4999..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/simplejson/scanner.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -Iterator based sre token scanner -""" -import sre_parse, sre_compile, sre_constants -from sre_constants import BRANCH, SUBPATTERN -from re import VERBOSE, MULTILINE, DOTALL -import re - -__all__ = ['Scanner', 'pattern'] - -FLAGS = (VERBOSE | MULTILINE | DOTALL) -class Scanner(object): - def __init__(self, lexicon, flags=FLAGS): - self.actions = [None] - # combine phrases into a compound pattern - s = sre_parse.Pattern() - s.flags = flags - p = [] - for idx, token in enumerate(lexicon): - phrase = token.pattern - try: - subpattern = sre_parse.SubPattern(s, - [(SUBPATTERN, (idx + 1, sre_parse.parse(phrase, flags)))]) - except sre_constants.error: - raise - p.append(subpattern) - self.actions.append(token) - - p = sre_parse.SubPattern(s, [(BRANCH, (None, p))]) - self.scanner = sre_compile.compile(p) - - - def iterscan(self, string, idx=0, context=None): - """ - Yield match, end_idx for each match - """ - match = self.scanner.scanner(string, idx).match - actions = self.actions - lastend = idx - end = len(string) - while True: - m = match() - if m is None: - break - matchbegin, matchend = m.span() - if lastend == matchend: - break - action = actions[m.lastindex] - if action is not None: - rval, next_pos = action(m, context) - if next_pos is not None and next_pos != matchend: - # "fast forward" the scanner - matchend = next_pos - match = self.scanner.scanner(string, matchend).match - yield rval, matchend - lastend = matchend - -def pattern(pattern, flags=FLAGS): - def decorator(fn): - fn.pattern = pattern - fn.regex = re.compile(pattern, flags) - return fn - return decorator diff --git a/WebKitTools/Scripts/webkitpy/tool/__init__.py b/WebKitTools/Scripts/webkitpy/tool/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/__init__.py b/WebKitTools/Scripts/webkitpy/tool/bot/__init__.py deleted file mode 100644 index ef65bee..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Required for Python to search this directory for module files diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask.py b/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask.py deleted file mode 100644 index ea12702..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask.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 webkitpy.common.system.executive import ScriptError -from webkitpy.common.net.layouttestresults import LayoutTestResults - - -class CommitQueueTaskDelegate(object): - def run_command(self, command): - raise NotImplementedError("subclasses must implement") - - def command_passed(self, message, patch): - raise NotImplementedError("subclasses must implement") - - def command_failed(self, message, script_error, patch): - raise NotImplementedError("subclasses must implement") - - def refetch_patch(self, patch): - raise NotImplementedError("subclasses must implement") - - def layout_test_results(self): - raise NotImplementedError("subclasses must implement") - - def report_flaky_tests(self, patch, flaky_tests): - raise NotImplementedError("subclasses must implement") - - -class CommitQueueTask(object): - def __init__(self, delegate, patch): - self._delegate = delegate - self._patch = patch - self._script_error = None - - def _validate(self): - # Bugs might get closed, or patches might be obsoleted or r-'d while the - # commit-queue is processing. - self._patch = self._delegate.refetch_patch(self._patch) - if self._patch.is_obsolete(): - return False - if self._patch.bug().is_closed(): - return False - if not self._patch.committer(): - return False - # Reviewer is not required. Missing reviewers will be caught during - # the ChangeLog check during landing. - return True - - def _run_command(self, command, success_message, failure_message): - try: - self._delegate.run_command(command) - self._delegate.command_passed(success_message, patch=self._patch) - return True - except ScriptError, e: - self._script_error = e - self.failure_status_id = self._delegate.command_failed(failure_message, script_error=self._script_error, patch=self._patch) - return False - - def _apply(self): - return self._run_command([ - "apply-attachment", - "--force-clean", - "--non-interactive", - self._patch.id(), - ], - "Applied patch", - "Patch does not apply") - - def _build(self): - return self._run_command([ - "build", - "--no-clean", - "--no-update", - "--build-style=both", - ], - "Built patch", - "Patch does not build") - - def _build_without_patch(self): - return self._run_command([ - "build", - "--force-clean", - "--no-update", - "--build-style=both", - ], - "Able to build without patch", - "Unable to build without patch") - - def _test(self): - return self._run_command([ - "build-and-test", - "--no-clean", - "--no-update", - # Notice that we don't pass --build, which means we won't build! - "--test", - "--non-interactive", - ], - "Passed tests", - "Patch does not pass tests") - - def _build_and_test_without_patch(self): - return self._run_command([ - "build-and-test", - "--force-clean", - "--no-update", - "--build", - "--test", - "--non-interactive", - ], - "Able to pass tests without patch", - "Unable to pass tests without patch (tree is red?)") - - def _failing_tests_from_last_run(self): - results = self._delegate.layout_test_results() - if not results: - return None - return results.failing_tests() - - def _land(self): - # Unclear if this should pass --quiet or not. If --parent-command always does the reporting, then it should. - return self._run_command([ - "land-attachment", - "--force-clean", - "--ignore-builders", - "--non-interactive", - "--parent-command=commit-queue", - self._patch.id(), - ], - "Landed patch", - "Unable to land patch") - - def _report_flaky_tests(self, flaky_tests): - self._delegate.report_flaky_tests(self._patch, flaky_tests) - - def _test_patch(self): - if self._patch.is_rollout(): - return True - if self._test(): - return True - - first_failing_tests = self._failing_tests_from_last_run() - if self._test(): - self._report_flaky_tests(first_failing_tests) - return True - - second_failing_tests = self._failing_tests_from_last_run() - if first_failing_tests != second_failing_tests: - self._report_flaky_tests(first_failing_tests + second_failing_tests) - return False - - if self._build_and_test_without_patch(): - raise self._script_error # The error from the previous ._test() run is real, report it. - return False # Tree must be red, just retry later. - - def run(self): - if not self._validate(): - return False - if not self._apply(): - raise self._script_error - if not self._build(): - if not self._build_without_patch(): - return False - raise self._script_error - if not self._test_patch(): - return False - # Make sure the patch is still valid before landing (e.g., make sure - # no one has set commit-queue- since we started working on the patch.) - if not self._validate(): - return False - if not self._land(): - raise self._script_error - return True diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py deleted file mode 100644 index 15a4a6b..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/commitqueuetask_unittest.py +++ /dev/null @@ -1,204 +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 datetime import datetime -import unittest - -from webkitpy.common.system.deprecated_logging import error, log -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.bot.commitqueuetask import * -from webkitpy.tool.mocktool import MockTool - - -class MockCommitQueue(CommitQueueTaskDelegate): - def __init__(self, error_plan): - self._error_plan = error_plan - - def run_command(self, command): - log("run_webkit_patch: %s" % command) - if self._error_plan: - error = self._error_plan.pop(0) - if error: - raise error - - def command_passed(self, success_message, patch): - log("command_passed: success_message='%s' patch='%s'" % ( - success_message, patch.id())) - - def command_failed(self, failure_message, script_error, patch): - log("command_failed: failure_message='%s' script_error='%s' patch='%s'" % ( - failure_message, script_error, patch.id())) - return 3947 - - def refetch_patch(self, patch): - return patch - - def layout_test_results(self): - return None - - def report_flaky_tests(self, patch, flaky_tests): - log("report_flaky_tests: patch='%s' flaky_tests='%s'" % (patch.id(), flaky_tests)) - - -class CommitQueueTaskTest(unittest.TestCase): - def _run_through_task(self, commit_queue, expected_stderr, expected_exception=None): - tool = MockTool(log_executive=True) - patch = tool.bugs.fetch_attachment(197) - task = CommitQueueTask(commit_queue, patch) - OutputCapture().assert_outputs(self, task.run, expected_stderr=expected_stderr, expected_exception=expected_exception) - - def test_success_case(self): - commit_queue = MockCommitQueue([]) - expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', 197] -command_passed: success_message='Applied patch' patch='197' -run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both'] -command_passed: success_message='Built patch' patch='197' -run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] -command_passed: success_message='Passed tests' patch='197' -run_webkit_patch: ['land-attachment', '--force-clean', '--ignore-builders', '--non-interactive', '--parent-command=commit-queue', 197] -command_passed: success_message='Landed patch' patch='197' -""" - self._run_through_task(commit_queue, expected_stderr) - - def test_apply_failure(self): - commit_queue = MockCommitQueue([ - ScriptError("MOCK apply failure"), - ]) - expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', 197] -command_failed: failure_message='Patch does not apply' script_error='MOCK apply failure' patch='197' -""" - self._run_through_task(commit_queue, expected_stderr, ScriptError) - - def test_build_failure(self): - commit_queue = MockCommitQueue([ - None, - ScriptError("MOCK build failure"), - ]) - expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', 197] -command_passed: success_message='Applied patch' patch='197' -run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both'] -command_failed: failure_message='Patch does not build' script_error='MOCK build failure' patch='197' -run_webkit_patch: ['build', '--force-clean', '--no-update', '--build-style=both'] -command_passed: success_message='Able to build without patch' patch='197' -""" - self._run_through_task(commit_queue, expected_stderr, ScriptError) - - def test_red_build_failure(self): - commit_queue = MockCommitQueue([ - None, - ScriptError("MOCK build failure"), - ScriptError("MOCK clean build failure"), - ]) - expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', 197] -command_passed: success_message='Applied patch' patch='197' -run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both'] -command_failed: failure_message='Patch does not build' script_error='MOCK build failure' patch='197' -run_webkit_patch: ['build', '--force-clean', '--no-update', '--build-style=both'] -command_failed: failure_message='Unable to build without patch' script_error='MOCK clean build failure' patch='197' -""" - self._run_through_task(commit_queue, expected_stderr) - - def test_flaky_test_failure(self): - commit_queue = MockCommitQueue([ - None, - None, - ScriptError("MOCK tests failure"), - ]) - expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', 197] -command_passed: success_message='Applied patch' patch='197' -run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both'] -command_passed: success_message='Built patch' patch='197' -run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] -command_failed: failure_message='Patch does not pass tests' script_error='MOCK tests failure' patch='197' -run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] -command_passed: success_message='Passed tests' patch='197' -report_flaky_tests: patch='197' flaky_tests='None' -run_webkit_patch: ['land-attachment', '--force-clean', '--ignore-builders', '--non-interactive', '--parent-command=commit-queue', 197] -command_passed: success_message='Landed patch' patch='197' -""" - self._run_through_task(commit_queue, expected_stderr) - - def test_test_failure(self): - commit_queue = MockCommitQueue([ - None, - None, - ScriptError("MOCK test failure"), - ScriptError("MOCK test failure again"), - ]) - expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', 197] -command_passed: success_message='Applied patch' patch='197' -run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both'] -command_passed: success_message='Built patch' patch='197' -run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] -command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure' patch='197' -run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] -command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure again' patch='197' -run_webkit_patch: ['build-and-test', '--force-clean', '--no-update', '--build', '--test', '--non-interactive'] -command_passed: success_message='Able to pass tests without patch' patch='197' -""" - self._run_through_task(commit_queue, expected_stderr, ScriptError) - - def test_red_test_failure(self): - commit_queue = MockCommitQueue([ - None, - None, - ScriptError("MOCK test failure"), - ScriptError("MOCK test failure again"), - ScriptError("MOCK clean test failure"), - ]) - expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', 197] -command_passed: success_message='Applied patch' patch='197' -run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both'] -command_passed: success_message='Built patch' patch='197' -run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] -command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure' patch='197' -run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] -command_failed: failure_message='Patch does not pass tests' script_error='MOCK test failure again' patch='197' -run_webkit_patch: ['build-and-test', '--force-clean', '--no-update', '--build', '--test', '--non-interactive'] -command_failed: failure_message='Unable to pass tests without patch (tree is red?)' script_error='MOCK clean test failure' patch='197' -""" - self._run_through_task(commit_queue, expected_stderr) - - def test_land_failure(self): - commit_queue = MockCommitQueue([ - None, - None, - None, - ScriptError("MOCK land failure"), - ]) - expected_stderr = """run_webkit_patch: ['apply-attachment', '--force-clean', '--non-interactive', 197] -command_passed: success_message='Applied patch' patch='197' -run_webkit_patch: ['build', '--no-clean', '--no-update', '--build-style=both'] -command_passed: success_message='Built patch' patch='197' -run_webkit_patch: ['build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] -command_passed: success_message='Passed tests' patch='197' -run_webkit_patch: ['land-attachment', '--force-clean', '--ignore-builders', '--non-interactive', '--parent-command=commit-queue', 197] -command_failed: failure_message='Unable to land patch' script_error='MOCK land failure' patch='197' -""" - self._run_through_task(commit_queue, expected_stderr, ScriptError) diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/feeders.py b/WebKitTools/Scripts/webkitpy/tool/bot/feeders.py deleted file mode 100644 index 046c4c1..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/feeders.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. - -from webkitpy.common.config.committervalidator import CommitterValidator -from webkitpy.common.system.deprecated_logging import log -from webkitpy.tool.grammar import pluralize - - -class AbstractFeeder(object): - def __init__(self, tool): - self._tool = tool - - def feed(self): - raise NotImplementedError("subclasses must implement") - - -class CommitQueueFeeder(AbstractFeeder): - queue_name = "commit-queue" - - def __init__(self, tool): - AbstractFeeder.__init__(self, tool) - self.committer_validator = CommitterValidator(self._tool.bugs) - - def _update_work_items(self, item_ids): - # FIXME: This is the last use of update_work_items, the commit-queue - # should move to feeding patches one at a time like the EWS does. - self._tool.status_server.update_work_items(self.queue_name, item_ids) - log("Feeding %s items %s" % (self.queue_name, item_ids)) - - def feed(self): - patches = self._validate_patches() - patches = sorted(patches, self._patch_cmp) - patch_ids = [patch.id() for patch in patches] - self._update_work_items(patch_ids) - - def _patches_for_bug(self, bug_id): - return self._tool.bugs.fetch_bug(bug_id).commit_queued_patches(include_invalid=True) - - def _validate_patches(self): - # Not using BugzillaQueries.fetch_patches_from_commit_queue() so we can reject patches with invalid committers/reviewers. - bug_ids = self._tool.bugs.queries.fetch_bug_ids_from_commit_queue() - all_patches = sum([self._patches_for_bug(bug_id) for bug_id in bug_ids], []) - return self.committer_validator.patches_after_rejecting_invalid_commiters_and_reviewers(all_patches) - - def _patch_cmp(self, a, b): - # Sort first by is_rollout, then by attach_date. - # Reversing the order so that is_rollout is first. - rollout_cmp = cmp(b.is_rollout(), a.is_rollout()) - if rollout_cmp != 0: - return rollout_cmp - return cmp(a.attach_date(), b.attach_date()) - - -class EWSFeeder(AbstractFeeder): - def __init__(self, tool): - self._ids_sent_to_server = set() - AbstractFeeder.__init__(self, tool) - - def feed(self): - ids_needing_review = set(self._tool.bugs.queries.fetch_attachment_ids_from_review_queue()) - new_ids = ids_needing_review.difference(self._ids_sent_to_server) - log("Feeding EWS (%s, %s new)" % (pluralize("r? patch", len(ids_needing_review)), len(new_ids))) - for attachment_id in new_ids: # Order doesn't really matter for the EWS. - self._tool.status_server.submit_to_ews(attachment_id) - self._ids_sent_to_server.add(attachment_id) diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/feeders_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/feeders_unittest.py deleted file mode 100644 index 5ce00b4..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/feeders_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. - -from datetime import datetime -import unittest - -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.bot.feeders import * -from webkitpy.tool.mocktool import MockTool - - -class FeedersTest(unittest.TestCase): - def test_commit_queue_feeder(self): - feeder = CommitQueueFeeder(MockTool()) - expected_stderr = u"""Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) -Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) -MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Rejecting patch 128 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/common/config/committers.py. - -- If you do not have committer rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags. - -- If you have committer 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 committer rights.' -MOCK: update_work_items: commit-queue [106, 197] -Feeding commit-queue items [106, 197] -""" - OutputCapture().assert_outputs(self, feeder.feed, expected_stderr=expected_stderr) - - def _mock_attachment(self, is_rollout, attach_date): - attachment = Mock() - attachment.is_rollout = lambda: is_rollout - attachment.attach_date = lambda: attach_date - return attachment - - def test_patch_cmp(self): - long_ago_date = datetime(1900, 1, 21) - recent_date = datetime(2010, 1, 21) - attachment1 = self._mock_attachment(is_rollout=False, attach_date=recent_date) - attachment2 = self._mock_attachment(is_rollout=False, attach_date=long_ago_date) - attachment3 = self._mock_attachment(is_rollout=True, attach_date=recent_date) - attachment4 = self._mock_attachment(is_rollout=True, attach_date=long_ago_date) - attachments = [attachment1, attachment2, attachment3, attachment4] - expected_sort = [attachment4, attachment3, attachment2, attachment1] - queue = CommitQueueFeeder(MockTool()) - attachments.sort(queue._patch_cmp) - self.assertEqual(attachments, expected_sort) diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/irc_command.py b/WebKitTools/Scripts/webkitpy/tool/bot/irc_command.py deleted file mode 100644 index a848472..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/irc_command.py +++ /dev/null @@ -1,109 +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 random -import webkitpy.common.config.irc as config_irc - -from webkitpy.common.checkout.changelog import view_source_url -from webkitpy.tool.bot.queueengine import TerminateQueue -from webkitpy.common.net.bugzilla import parse_bug_id -from webkitpy.common.system.executive import ScriptError - -# FIXME: Merge with Command? -class IRCCommand(object): - def execute(self, nick, args, tool, sheriff): - raise NotImplementedError, "subclasses must implement" - - -class LastGreenRevision(IRCCommand): - def execute(self, nick, args, tool, sheriff): - return "%s: %s" % (nick, - view_source_url(tool.buildbot.last_green_revision())) - - -class Restart(IRCCommand): - def execute(self, nick, args, tool, sheriff): - tool.irc().post("Restarting...") - raise TerminateQueue() - - -class Rollout(IRCCommand): - def execute(self, nick, args, tool, sheriff): - if len(args) < 2: - tool.irc().post("%s: Usage: SVN_REVISION REASON" % nick) - return - svn_revision = args[0].lstrip("r") - rollout_reason = " ".join(args[1:]) - tool.irc().post("Preparing rollout for r%s..." % svn_revision) - try: - complete_reason = "%s (Requested by %s on %s)." % ( - rollout_reason, nick, config_irc.channel) - bug_id = sheriff.post_rollout_patch(svn_revision, complete_reason) - bug_url = tool.bugs.bug_url_for_bug_id(bug_id) - tool.irc().post("%s: Created rollout: %s" % (nick, bug_url)) - except ScriptError, e: - tool.irc().post("%s: Failed to create rollout patch:" % nick) - tool.irc().post("%s" % e) - bug_id = parse_bug_id(e.output) - if bug_id: - tool.irc().post("Ugg... Might have created %s" % - tool.bugs.bug_url_for_bug_id(bug_id)) - - -class Help(IRCCommand): - def execute(self, nick, args, tool, sheriff): - return "%s: Available commands: %s" % (nick, ", ".join(commands.keys())) - - -class Hi(IRCCommand): - def execute(self, nick, args, tool, sheriff): - quips = tool.bugs.quips() - quips.append('"Only you can prevent forest fires." -- Smokey the Bear') - return random.choice(quips) - - -class Eliza(IRCCommand): - therapist = None - - def __init__(self): - if not self.therapist: - import webkitpy.thirdparty.autoinstalled.eliza as eliza - Eliza.therapist = eliza.eliza() - - def execute(self, nick, args, tool, sheriff): - return "%s: %s" % (nick, self.therapist.respond(" ".join(args))) - - -# FIXME: Lame. We should have an auto-registering CommandCenter. -commands = { - "last-green-revision": LastGreenRevision, - "restart": Restart, - "rollout": Rollout, - "help": Help, - "hi": Hi, -} diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/irc_command_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/irc_command_unittest.py deleted file mode 100644 index 7aeb6a0..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/irc_command_unittest.py +++ /dev/null @@ -1,38 +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.tool.bot.irc_command import * - - -class IRCCommandTest(unittest.TestCase): - def test_eliza(self): - eliza = Eliza() - eliza.execute("tom", "hi", None, None) - eliza.execute("tom", "bye", None, None) diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py deleted file mode 100644 index 8b016e8..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine.py +++ /dev/null @@ -1,165 +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. - -import os -import time -import traceback - -from datetime import datetime, timedelta - -from webkitpy.common.system.executive import ScriptError -from webkitpy.common.system.deprecated_logging import log, OutputTee - - -class TerminateQueue(Exception): - pass - - -class QueueEngineDelegate: - def queue_log_path(self): - raise NotImplementedError, "subclasses must implement" - - def work_item_log_path(self, work_item): - raise NotImplementedError, "subclasses must implement" - - def begin_work_queue(self): - raise NotImplementedError, "subclasses must implement" - - def should_continue_work_queue(self): - raise NotImplementedError, "subclasses must implement" - - def next_work_item(self): - raise NotImplementedError, "subclasses must implement" - - def should_proceed_with_work_item(self, work_item): - # returns (safe_to_proceed, waiting_message, patch) - raise NotImplementedError, "subclasses must implement" - - def process_work_item(self, work_item): - raise NotImplementedError, "subclasses must implement" - - def handle_unexpected_error(self, work_item, message): - raise NotImplementedError, "subclasses must implement" - - -class QueueEngine: - def __init__(self, name, delegate, wakeup_event): - self._name = name - self._delegate = delegate - self._wakeup_event = wakeup_event - self._output_tee = OutputTee() - - log_date_format = "%Y-%m-%d %H:%M:%S" - sleep_duration_text = "2 mins" # This could be generated from seconds_to_sleep - seconds_to_sleep = 120 - handled_error_code = 2 - - # Child processes exit with a special code to the parent queue process can detect the error was handled. - @classmethod - def exit_after_handled_error(cls, error): - log(error) - exit(cls.handled_error_code) - - def run(self): - self._begin_logging() - - self._delegate.begin_work_queue() - while (self._delegate.should_continue_work_queue()): - try: - self._ensure_work_log_closed() - work_item = self._delegate.next_work_item() - if not work_item: - self._sleep("No work item.") - continue - if not self._delegate.should_proceed_with_work_item(work_item): - self._sleep("Not proceeding with work item.") - continue - - # FIXME: Work logs should not depend on bug_id specificaly. - # This looks fixed, no? - self._open_work_log(work_item) - try: - if not self._delegate.process_work_item(work_item): - log("Unable to process work item.") - continue - except ScriptError, e: - # Use a special exit code to indicate that the error was already - # handled in the child process and we should just keep looping. - if e.exit_code == self.handled_error_code: - continue - message = "Unexpected failure when processing patch! Please file a bug against webkit-patch.\n%s" % e.message_with_output() - self._delegate.handle_unexpected_error(work_item, message) - except TerminateQueue, e: - self._stopping("TerminateQueue exception received.") - return 0 - except KeyboardInterrupt, e: - self._stopping("User terminated queue.") - return 1 - except Exception, e: - traceback.print_exc() - # Don't try tell the status bot, in case telling it causes an exception. - self._sleep("Exception while preparing queue") - self._stopping("Delegate terminated queue.") - return 0 - - def _stopping(self, message): - log("\n%s" % message) - self._delegate.stop_work_queue(message) - # Be careful to shut down our OutputTee or the unit tests will be unhappy. - self._ensure_work_log_closed() - self._output_tee.remove_log(self._queue_log) - - def _begin_logging(self): - self._queue_log = self._output_tee.add_log(self._delegate.queue_log_path()) - self._work_log = None - - def _open_work_log(self, work_item): - work_item_log_path = self._delegate.work_item_log_path(work_item) - if not work_item_log_path: - return - self._work_log = self._output_tee.add_log(work_item_log_path) - - def _ensure_work_log_closed(self): - # If we still have a bug log open, close it. - if self._work_log: - self._output_tee.remove_log(self._work_log) - self._work_log = None - - def _now(self): - """Overriden by the unit tests to allow testing _sleep_message""" - return datetime.now() - - def _sleep_message(self, message): - wake_time = self._now() + timedelta(seconds=self.seconds_to_sleep) - return "%s Sleeping until %s (%s)." % (message, wake_time.strftime(self.log_date_format), self.sleep_duration_text) - - def _sleep(self, message): - log(self._sleep_message(message)) - self._wakeup_event.wait(self.seconds_to_sleep) - self._wakeup_event.clear() diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py deleted file mode 100644 index 37d8502..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/queueengine_unittest.py +++ /dev/null @@ -1,209 +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 datetime -import os -import shutil -import tempfile -import threading -import unittest - -from webkitpy.common.system.executive import ScriptError -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.tool.bot.queueengine import QueueEngine, QueueEngineDelegate, TerminateQueue - - -class LoggingDelegate(QueueEngineDelegate): - def __init__(self, test): - self._test = test - self._callbacks = [] - self._run_before = False - self.stop_message = None - - expected_callbacks = [ - 'queue_log_path', - 'begin_work_queue', - 'should_continue_work_queue', - 'next_work_item', - 'should_proceed_with_work_item', - 'work_item_log_path', - 'process_work_item', - 'should_continue_work_queue', - 'stop_work_queue', - ] - - def record(self, method_name): - self._callbacks.append(method_name) - - def queue_log_path(self): - self.record("queue_log_path") - return os.path.join(self._test.temp_dir, "queue_log_path") - - def work_item_log_path(self, work_item): - self.record("work_item_log_path") - return os.path.join(self._test.temp_dir, "work_log_path", "%s.log" % work_item) - - def begin_work_queue(self): - self.record("begin_work_queue") - - def should_continue_work_queue(self): - self.record("should_continue_work_queue") - if not self._run_before: - self._run_before = True - return True - return False - - def next_work_item(self): - self.record("next_work_item") - return "work_item" - - def should_proceed_with_work_item(self, work_item): - self.record("should_proceed_with_work_item") - self._test.assertEquals(work_item, "work_item") - fake_patch = { 'bug_id' : 42 } - return (True, "waiting_message", fake_patch) - - def process_work_item(self, work_item): - self.record("process_work_item") - self._test.assertEquals(work_item, "work_item") - return True - - def handle_unexpected_error(self, work_item, message): - self.record("handle_unexpected_error") - self._test.assertEquals(work_item, "work_item") - - def stop_work_queue(self, message): - self.record("stop_work_queue") - self.stop_message = message - - -class RaisingDelegate(LoggingDelegate): - def __init__(self, test, exception): - LoggingDelegate.__init__(self, test) - self._exception = exception - - def process_work_item(self, work_item): - self.record("process_work_item") - raise self._exception - - -class NotSafeToProceedDelegate(LoggingDelegate): - def should_proceed_with_work_item(self, work_item): - self.record("should_proceed_with_work_item") - self._test.assertEquals(work_item, "work_item") - return False - - -class FastQueueEngine(QueueEngine): - def __init__(self, delegate): - QueueEngine.__init__(self, "fast-queue", delegate, threading.Event()) - - # No sleep for the wicked. - seconds_to_sleep = 0 - - def _sleep(self, message): - pass - - -class QueueEngineTest(unittest.TestCase): - def test_trivial(self): - delegate = LoggingDelegate(self) - self._run_engine(delegate) - self.assertEquals(delegate.stop_message, "Delegate terminated queue.") - self.assertEquals(delegate._callbacks, LoggingDelegate.expected_callbacks) - self.assertTrue(os.path.exists(os.path.join(self.temp_dir, "queue_log_path"))) - self.assertTrue(os.path.exists(os.path.join(self.temp_dir, "work_log_path", "work_item.log"))) - - def test_unexpected_error(self): - delegate = RaisingDelegate(self, ScriptError(exit_code=3)) - self._run_engine(delegate) - expected_callbacks = LoggingDelegate.expected_callbacks[:] - work_item_index = expected_callbacks.index('process_work_item') - # The unexpected error should be handled right after process_work_item starts - # but before any other callback. Otherwise callbacks should be normal. - expected_callbacks.insert(work_item_index + 1, 'handle_unexpected_error') - self.assertEquals(delegate._callbacks, expected_callbacks) - - def test_handled_error(self): - delegate = RaisingDelegate(self, ScriptError(exit_code=QueueEngine.handled_error_code)) - self._run_engine(delegate) - self.assertEquals(delegate._callbacks, LoggingDelegate.expected_callbacks) - - def _run_engine(self, delegate, engine=None, termination_message=None): - if not engine: - engine = QueueEngine("test-queue", delegate, threading.Event()) - if not termination_message: - termination_message = "Delegate terminated queue." - expected_stderr = "\n%s\n" % termination_message - OutputCapture().assert_outputs(self, engine.run, [], expected_stderr=expected_stderr) - - def _test_terminating_queue(self, exception, termination_message): - work_item_index = LoggingDelegate.expected_callbacks.index('process_work_item') - # The terminating error should be handled right after process_work_item. - # There should be no other callbacks after stop_work_queue. - expected_callbacks = LoggingDelegate.expected_callbacks[:work_item_index + 1] - expected_callbacks.append("stop_work_queue") - - delegate = RaisingDelegate(self, exception) - self._run_engine(delegate, termination_message=termination_message) - - self.assertEquals(delegate._callbacks, expected_callbacks) - self.assertEquals(delegate.stop_message, termination_message) - - def test_terminating_error(self): - self._test_terminating_queue(KeyboardInterrupt(), "User terminated queue.") - self._test_terminating_queue(TerminateQueue(), "TerminateQueue exception received.") - - def test_not_safe_to_proceed(self): - delegate = NotSafeToProceedDelegate(self) - self._run_engine(delegate, engine=FastQueueEngine(delegate)) - expected_callbacks = LoggingDelegate.expected_callbacks[:] - expected_callbacks.remove('work_item_log_path') - expected_callbacks.remove('process_work_item') - self.assertEquals(delegate._callbacks, expected_callbacks) - - def test_now(self): - """Make sure there are no typos in the QueueEngine.now() method.""" - engine = QueueEngine("test", None, None) - self.assertTrue(isinstance(engine._now(), datetime.datetime)) - - def test_sleep_message(self): - engine = QueueEngine("test", None, None) - engine._now = lambda: datetime.datetime(2010, 1, 1) - expected_sleep_message = "MESSAGE Sleeping until 2010-01-01 00:02:00 (2 mins)." - self.assertEqual(engine._sleep_message("MESSAGE"), expected_sleep_message) - - def setUp(self): - self.temp_dir = tempfile.mkdtemp(suffix="work_queue_test_logs") - - def tearDown(self): - shutil.rmtree(self.temp_dir) - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/sheriff.py b/WebKitTools/Scripts/webkitpy/tool/bot/sheriff.py deleted file mode 100644 index da506bc..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/sheriff.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. - -from webkitpy.common.checkout.changelog import view_source_url -from webkitpy.common.net.bugzilla import parse_bug_id -from webkitpy.common.system.deprecated_logging import log -from webkitpy.common.system.executive import ScriptError -from webkitpy.tool.grammar import join_with_separators - - -class Sheriff(object): - def __init__(self, tool, sheriffbot): - self._tool = tool - self._sheriffbot = sheriffbot - - def post_irc_warning(self, commit_info, builders): - irc_nicknames = sorted([party.irc_nickname for - party in commit_info.responsible_parties() - if party.irc_nickname]) - irc_prefix = ": " if irc_nicknames else "" - irc_message = "%s%s%s might have broken %s" % ( - ", ".join(irc_nicknames), - irc_prefix, - view_source_url(commit_info.revision()), - join_with_separators([builder.name() for builder in builders])) - - self._tool.irc().post(irc_message) - - def post_rollout_patch(self, svn_revision, rollout_reason): - # Ensure that svn_revision is a number (and not an option to - # create-rollout). - try: - svn_revision = int(svn_revision) - except: - raise ScriptError(message="Invalid svn revision number \"%s\"." - % svn_revision) - - if rollout_reason.startswith("-"): - raise ScriptError(message="The rollout reason may not begin " - "with - (\"%s\")." % rollout_reason) - - output = self._sheriffbot.run_webkit_patch([ - "create-rollout", - "--force-clean", - # In principle, we should pass --non-interactive here, but it - # turns out that create-rollout doesn't need it yet. We can't - # pass it prophylactically because we reject unrecognized command - # line switches. - "--parent-command=sheriff-bot", - svn_revision, - rollout_reason, - ]) - return parse_bug_id(output) - - def post_blame_comment_on_bug(self, commit_info, builders, tests): - if not commit_info.bug_id(): - return - comment = "%s might have broken %s" % ( - view_source_url(commit_info.revision()), - join_with_separators([builder.name() for builder in builders])) - if tests: - comment += "\nThe following tests are not passing:\n" - comment += "\n".join(tests) - self._tool.bugs.post_comment_to_bug(commit_info.bug_id(), - comment, - cc=self._sheriffbot.watchers) diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/sheriff_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/sheriff_unittest.py deleted file mode 100644 index 690af1f..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/sheriff_unittest.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. - -import os -import unittest - -from webkitpy.common.net.buildbot import Builder -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.bot.sheriff import Sheriff -from webkitpy.tool.mocktool import MockTool - - -class MockSheriffBot(object): - name = "mock-sheriff-bot" - watchers = [ - "watcher@example.com", - ] - - def run_webkit_patch(self, args): - return "Created bug https://bugs.webkit.org/show_bug.cgi?id=36936\n" - - -class SheriffTest(unittest.TestCase): - def test_post_blame_comment_on_bug(self): - def run(): - sheriff = Sheriff(MockTool(), MockSheriffBot()) - builders = [ - Builder("Foo", None), - Builder("Bar", None), - ] - commit_info = Mock() - commit_info.bug_id = lambda: None - commit_info.revision = lambda: 4321 - # Should do nothing with no bug_id - sheriff.post_blame_comment_on_bug(commit_info, builders, []) - sheriff.post_blame_comment_on_bug(commit_info, builders, ["mock-test-1", "mock-test-2"]) - # Should try to post a comment to the bug, but MockTool.bugs does nothing. - commit_info.bug_id = lambda: 1234 - sheriff.post_blame_comment_on_bug(commit_info, builders, []) - sheriff.post_blame_comment_on_bug(commit_info, builders, ["mock-test-1"]) - sheriff.post_blame_comment_on_bug(commit_info, builders, ["mock-test-1", "mock-test-2"]) - - expected_stderr = u"""MOCK bug comment: bug_id=1234, cc=['watcher@example.com'] ---- Begin comment --- -http://trac.webkit.org/changeset/4321 might have broken Foo and Bar ---- End comment --- - -MOCK bug comment: bug_id=1234, cc=['watcher@example.com'] ---- Begin comment --- -http://trac.webkit.org/changeset/4321 might have broken Foo and Bar -The following tests are not passing: -mock-test-1 ---- End comment --- - -MOCK bug comment: bug_id=1234, cc=['watcher@example.com'] ---- Begin comment --- -http://trac.webkit.org/changeset/4321 might have broken Foo and Bar -The following tests are not passing: -mock-test-1 -mock-test-2 ---- End comment --- - -""" - OutputCapture().assert_outputs(self, run, expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/sheriffircbot.py b/WebKitTools/Scripts/webkitpy/tool/bot/sheriffircbot.py deleted file mode 100644 index de77222..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/sheriffircbot.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 webkitpy.tool.bot.irc_command as irc_command - -from webkitpy.common.net.irc.ircbot import IRCBotDelegate -from webkitpy.common.thread.threadedmessagequeue import ThreadedMessageQueue - - -class _IRCThreadTearoff(IRCBotDelegate): - def __init__(self, password, message_queue, wakeup_event): - self._password = password - self._message_queue = message_queue - self._wakeup_event = wakeup_event - - # IRCBotDelegate methods - - def irc_message_received(self, nick, message): - self._message_queue.post([nick, message]) - self._wakeup_event.set() - - def irc_nickname(self): - return "sheriffbot" - - def irc_password(self): - return self._password - - -class SheriffIRCBot(object): - def __init__(self, tool, sheriff): - self._tool = tool - self._sheriff = sheriff - self._message_queue = ThreadedMessageQueue() - - def irc_delegate(self): - return _IRCThreadTearoff(self._tool.irc_password, - self._message_queue, - self._tool.wakeup_event) - - def process_message(self, message): - (nick, request) = message - tokenized_request = request.strip().split(" ") - if not tokenized_request: - return - command = irc_command.commands.get(tokenized_request[0]) - args = tokenized_request[1:] - if not command: - # Give the peoples someone to talk with. - command = irc_command.Eliza - args = tokenized_request - response = command().execute(nick, args, self._tool, self._sheriff) - if response: - self._tool.irc().post(response) - - def process_pending_messages(self): - (messages, is_running) = self._message_queue.take_all() - for message in messages: - self.process_message(message) diff --git a/WebKitTools/Scripts/webkitpy/tool/bot/sheriffircbot_unittest.py b/WebKitTools/Scripts/webkitpy/tool/bot/sheriffircbot_unittest.py deleted file mode 100644 index 08023bd..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/bot/sheriffircbot_unittest.py +++ /dev/null @@ -1,95 +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 random - -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.tool.bot.sheriff import Sheriff -from webkitpy.tool.bot.sheriffircbot import SheriffIRCBot -from webkitpy.tool.bot.sheriff_unittest import MockSheriffBot -from webkitpy.tool.mocktool import MockTool - - -def run(message): - tool = MockTool() - tool.ensure_irc_connected(None) - bot = SheriffIRCBot(tool, Sheriff(tool, MockSheriffBot())) - bot._message_queue.post(["mock_nick", message]) - bot.process_pending_messages() - - -class SheriffIRCBotTest(unittest.TestCase): - def test_hi(self): - random.seed(23324) - expected_stderr = 'MOCK: irc.post: "Only you can prevent forest fires." -- Smokey the Bear\n' - OutputCapture().assert_outputs(self, run, args=["hi"], expected_stderr=expected_stderr) - - def test_help(self): - expected_stderr = "MOCK: irc.post: mock_nick: Available commands: rollout, hi, help, restart, last-green-revision\n" - OutputCapture().assert_outputs(self, run, args=["help"], expected_stderr=expected_stderr) - - def test_lgr(self): - expected_stderr = "MOCK: irc.post: mock_nick: http://trac.webkit.org/changeset/9479\n" - OutputCapture().assert_outputs(self, run, args=["last-green-revision"], expected_stderr=expected_stderr) - - def test_rollout(self): - expected_stderr = "MOCK: irc.post: Preparing rollout for r21654...\nMOCK: irc.post: mock_nick: Created rollout: http://example.com/36936\n" - OutputCapture().assert_outputs(self, run, args=["rollout 21654 This patch broke the world"], expected_stderr=expected_stderr) - - def test_rollout_with_r_in_svn_revision(self): - expected_stderr = "MOCK: irc.post: Preparing rollout for r21654...\nMOCK: irc.post: mock_nick: Created rollout: http://example.com/36936\n" - OutputCapture().assert_outputs(self, run, args=["rollout r21654 This patch broke the world"], expected_stderr=expected_stderr) - - def test_rollout_bananas(self): - expected_stderr = "MOCK: irc.post: mock_nick: Usage: SVN_REVISION REASON\n" - OutputCapture().assert_outputs(self, run, args=["rollout bananas"], expected_stderr=expected_stderr) - - def test_rollout_invalidate_revision(self): - expected_stderr = ("MOCK: irc.post: Preparing rollout for r--component=Tools...\n" - "MOCK: irc.post: mock_nick: Failed to create rollout patch:\n" - "MOCK: irc.post: Invalid svn revision number \"--component=Tools\".\n") - OutputCapture().assert_outputs(self, run, - args=["rollout " - "--component=Tools 21654"], - expected_stderr=expected_stderr) - - def test_rollout_invalidate_reason(self): - expected_stderr = ("MOCK: irc.post: Preparing rollout for " - "r21654...\nMOCK: irc.post: mock_nick: Failed to " - "create rollout patch:\nMOCK: irc.post: The rollout" - " reason may not begin with - (\"-bad (Requested " - "by mock_nick on #webkit).\").\n") - OutputCapture().assert_outputs(self, run, - args=["rollout " - "21654 -bad"], - expected_stderr=expected_stderr) - - def test_rollout_no_reason(self): - expected_stderr = "MOCK: irc.post: mock_nick: Usage: SVN_REVISION REASON\n" - OutputCapture().assert_outputs(self, run, args=["rollout 21654"], expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/__init__.py b/WebKitTools/Scripts/webkitpy/tool/commands/__init__.py deleted file mode 100644 index d2aa503..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# Required for Python to search this directory for module files - -from webkitpy.tool.commands.download import * -from webkitpy.tool.commands.earlywarningsystem import * -from webkitpy.tool.commands.openbugs import OpenBugs -from webkitpy.tool.commands.prettydiff import PrettyDiff -from webkitpy.tool.commands.queries import * -from webkitpy.tool.commands.queues import * -from webkitpy.tool.commands.rebaseline import Rebaseline -from webkitpy.tool.commands.rebaselineserver import RebaselineServer -from webkitpy.tool.commands.sheriffbot import * -from webkitpy.tool.commands.upload import * diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py b/WebKitTools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.py deleted file mode 100644 index fc5a794..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/abstractsequencedcommand.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. - -from webkitpy.tool.commands.stepsequence import StepSequence -from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand - - -class AbstractSequencedCommand(AbstractDeclarativeCommand): - steps = None - def __init__(self): - self._sequence = StepSequence(self.steps) - AbstractDeclarativeCommand.__init__(self, self._sequence.options()) - - def _prepare_state(self, options, args, tool): - return None - - def execute(self, options, args, tool): - self._sequence.run_and_handle_errors(tool, options, self._prepare_state(options, args, tool)) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/commandtest.py b/WebKitTools/Scripts/webkitpy/tool/commands/commandtest.py deleted file mode 100644 index adc6d81..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/commandtest.py +++ /dev/null @@ -1,48 +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.system.outputcapture import OutputCapture -from webkitpy.tool.mocktool import MockOptions, MockTool - -class CommandsTest(unittest.TestCase): - def assert_execute_outputs(self, command, args, expected_stdout="", expected_stderr="", options=MockOptions(), tool=MockTool()): - options.blocks = True - options.cc = 'MOCK cc' - options.component = 'MOCK component' - options.confirm = True - options.email = 'MOCK email' - options.git_commit = 'MOCK git commit' - options.obsolete_patches = True - options.open_bug = True - options.port = 'MOCK port' - options.quiet = True - options.reviewer = 'MOCK reviewer' - command.bind_to_tool(tool) - OutputCapture().assert_outputs(self, command.execute, [options, args, tool], expected_stdout=expected_stdout, expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/index.html b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/index.html deleted file mode 100644 index 5b58301..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/index.html +++ /dev/null @@ -1,180 +0,0 @@ -<!DOCTYPE html> -<!-- - 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. ---> -<html> -<head> - <title>Layout Test Rebaseline Server</title> - <link rel="stylesheet" href="/main.css" type="text/css"> - <script src="/util.js"></script> - <script src="/loupe.js"></script> - <script src="/main.js"></script> - <script src="/queue.js"></script> -</head> -<body class="loading"> - -<pre id="log" style="display: none"></pre> -<div id="queue" style="display: none"> - Queue: - <select id="queue-select" size="10"></select> - <button id="remove-queue-selection">Remove selection</button> - <button id="rebaseline-queue">Rebaseline queue</button> -</div> - -<div id="header"> - <div id="controls"> - <!-- Add a dummy <select> node so that this lines up with the text on the left --> - <select style="visibility: hidden"></select> - <span id="toggle-log" class="link">Log</span> - <span class="divider">|</span> - <a href="/quitquitquit">Exit</a> - </div> - - <span id="selectors"> - <label> - Failure type: - <select id="failure-type-selector"></select> - </label> - - <label> - Directory: - <select id="directory-selector"></select> - </label> - - <label> - Test: - <select id="test-selector"></select> - </label> - </span> - - <a id="test-link" target="_blank">View test</a> - - <span id="nav-buttons"> - <button id="previous-test">«</button> - <span id="test-index"></span> of <span id="test-count"></span> - <button id="next-test">»</button> - </span> -</div> - -<table id="test-output"> - <thead id="labels"> - <tr> - <th>Expected</th> - <th>Actual</th> - <th>Diff</th> - </tr> - </thead> - <tbody id="image-outputs" style="display: none"> - <tr> - <td colspan="3"><h2>Image</h2></td> - </tr> - <tr> - <td><img id="expected-image"></td> - <td><img id="actual-image"></td> - <td> - <canvas id="diff-canvas" width="800" height="600"></canvas> - <div id="diff-checksum" style="display: none"> - <h3>Checksum mismatch</h3> - Expected: <span id="expected-checksum"></span><br> - Actual: <span id="actual-checksum"></span> - </div> - </td> - </tr> - </tbody> - <tbody id="text-outputs" style="display: none"> - <tr> - <td colspan="3"><h2>Text</h2></td> - </tr> - <tr> - <td><pre id="expected-text"></pre></td> - <td><pre id="actual-text"></pre></td> - <td><pre id="diff-text"><pre></td> - </tr> - </tbody> -</table> - -<div id="footer"> - <label>State: <span id="state"></span></label> - <label>Existing baselines: <span id="current-baselines"></span></label> - <label> - Baseline target: - <select id="baseline-target"></select> - </label> - <label> - Move current baselines to: - <select id="baseline-move-to"> - <option value="none">Nowhere (replace)</option> - </select> - </label> - - <!-- Add a dummy <button> node so that this lines up with the text on the right --> - <button style="visibility: hidden; padding-left: 0; padding-right: 0;"></button> - - <div id="action-buttons"> - <span id="toggle-queue" class="link">Queue</span> - <button id="add-to-rebaseline-queue">Add to rebaseline queue</button> - </div> -</div> - -<table id="loupe" style="display: none"> - <tr> - <td colspan="3" id="loupe-info"> - <span id="loupe-close" class="link">Close</span> - <label>Coordinate: <span id="loupe-coordinate"></span></label> - </td> - </tr> - <tr> - <td> - <div class="loupe-container"> - <canvas id="expected-loupe" width="210" height="210"></canvas> - <div class="center-highlight"></div> - </div> - </td> - <td> - <div class="loupe-container"> - <canvas id="actual-loupe" width="210" height="210"></canvas> - <div class="center-highlight"></div> - </div> - </td> - <td> - <div class="loupe-container"> - <canvas id="diff-loupe" width="210" height="210"></canvas> - <div class="center-highlight"></div> - </div> - </td> - </tr> - <tr id="loupe-colors"> - <td><label>Exp. color: <span id="expected-loupe-color"></span></label></td> - <td><label>Actual color: <span id="actual-loupe-color"></span></label></td> - <td><label>Diff color: <span id="diff-loupe-color"></span></label></td> - </tr> -</table> - -</body> -</html> diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/loupe.js b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/loupe.js deleted file mode 100644 index 41f977a..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/loupe.js +++ /dev/null @@ -1,144 +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. - */ - -var LOUPE_MAGNIFICATION_FACTOR = 10; - -function Loupe() -{ - this._node = $('loupe'); - this._currentCornerX = -1; - this._currentCornerY = -1; - - var self = this; - - function handleOutputClick(event) { self._handleOutputClick(event); } - $('expected-image').addEventListener('click', handleOutputClick); - $('actual-image').addEventListener('click', handleOutputClick); - $('diff-canvas').addEventListener('click', handleOutputClick); - - function handleLoupeClick(event) { self._handleLoupeClick(event); } - $('expected-loupe').addEventListener('click', handleLoupeClick); - $('actual-loupe').addEventListener('click', handleLoupeClick); - $('diff-loupe').addEventListener('click', handleLoupeClick); - - function hide(event) { self.hide(); } - $('loupe-close').addEventListener('click', hide); -} - -Loupe.prototype._handleOutputClick = function(event) -{ - // The -1 compensates for the border around the image/canvas. - this._showFor(event.offsetX - 1, event.offsetY - 1); -}; - -Loupe.prototype._handleLoupeClick = function(event) -{ - var deltaX = Math.floor(event.offsetX/LOUPE_MAGNIFICATION_FACTOR); - var deltaY = Math.floor(event.offsetY/LOUPE_MAGNIFICATION_FACTOR); - - this._showFor( - this._currentCornerX + deltaX, this._currentCornerY + deltaY); -} - -Loupe.prototype.hide = function() -{ - this._node.style.display = 'none'; -}; - -Loupe.prototype._showFor = function(x, y) -{ - this._fillFromImage(x, y, 'expected', $('expected-image')); - this._fillFromImage(x, y, 'actual', $('actual-image')); - this._fillFromCanvas(x, y, 'diff', $('diff-canvas')); - - this._node.style.display = ''; -}; - -Loupe.prototype._fillFromImage = function(x, y, type, sourceImage) -{ - var tempCanvas = document.createElement('canvas'); - tempCanvas.width = sourceImage.width; - tempCanvas.height = sourceImage.height; - var tempContext = tempCanvas.getContext('2d'); - - tempContext.drawImage(sourceImage, 0, 0); - - this._fillFromCanvas(x, y, type, tempCanvas); -}; - -Loupe.prototype._fillFromCanvas = function(x, y, type, canvas) -{ - var context = canvas.getContext('2d'); - var sourceImageData = - context.getImageData(0, 0, canvas.width, canvas.height); - - var targetCanvas = $(type + '-loupe'); - var targetContext = targetCanvas.getContext('2d'); - targetContext.fillStyle = 'rgba(255, 255, 255, 1)'; - targetContext.fillRect(0, 0, targetCanvas.width, targetCanvas.height); - - var sourceXOffset = (targetCanvas.width/LOUPE_MAGNIFICATION_FACTOR - 1)/2; - var sourceYOffset = (targetCanvas.height/LOUPE_MAGNIFICATION_FACTOR - 1)/2; - - function readPixelComponent(x, y, component) { - var offset = (y * sourceImageData.width + x) * 4 + component; - return sourceImageData.data[offset]; - } - - for (var i = -sourceXOffset; i <= sourceXOffset; i++) { - for (var j = -sourceYOffset; j <= sourceYOffset; j++) { - var sourceX = x + i; - var sourceY = y + j; - - var sourceR = readPixelComponent(sourceX, sourceY, 0); - var sourceG = readPixelComponent(sourceX, sourceY, 1); - var sourceB = readPixelComponent(sourceX, sourceY, 2); - var sourceA = readPixelComponent(sourceX, sourceY, 3)/255; - sourceA = Math.round(sourceA * 10)/10; - - var targetX = (i + sourceXOffset) * LOUPE_MAGNIFICATION_FACTOR; - var targetY = (j + sourceYOffset) * LOUPE_MAGNIFICATION_FACTOR; - var colorString = - sourceR + ', ' + sourceG + ', ' + sourceB + ', ' + sourceA; - targetContext.fillStyle = 'rgba(' + colorString + ')'; - targetContext.fillRect( - targetX, targetY, - LOUPE_MAGNIFICATION_FACTOR, LOUPE_MAGNIFICATION_FACTOR); - - if (i == 0 && j == 0) { - $('loupe-coordinate').textContent = sourceX + ', ' + sourceY; - $(type + '-loupe-color').textContent = colorString; - } - } - } - - this._currentCornerX = x - sourceXOffset; - this._currentCornerY = y - sourceYOffset; -}; diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.css b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.css deleted file mode 100644 index 62afda6..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.css +++ /dev/null @@ -1,292 +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. - */ - -body { - font-size: 12px; - font-family: Helvetica, Arial, sans-serif; - padding: 0; - margin: 0; -} - -.loading { - opacity: 0.5; -} - -div { - margin: 0; -} - -a, .link { - color: #aaf; - text-decoration: underline; - cursor: pointer; -} - -.link.selected { - color: #fff; - font-weight: bold; - text-decoration: none; -} - -#log, -#queue { - padding: .25em 0 0 .25em; - position: absolute; - right: 0; - height: 200px; - overflow: auto; - background: #fff; - -webkit-box-shadow: 1px 1px 5px rgba(0, 0, 0, .5); -} - -#log { - top: 2em; - width: 500px; -} - -#queue { - bottom: 3em; - width: 400px; -} - -#queue-select { - display: block; - width: 390px; -} - -#header, -#footer { - padding: .5em 1em; - background: #333; - color: #fff; - -webkit-box-shadow: 0 1px 5px rgba(0, 0, 0, 0.5); -} - -#header { - margin-bottom: 1em; -} - -#header .divider, -#footer .divider { - opacity: .3; - padding: 0 .5em; -} - -#header label, -#footer label { - padding-right: 1em; - color: #ccc; -} - -#test-link { - margin-right: 1em; -} - -#header label span, -#footer label span { - color: #fff; - font-weight: bold; -} - -#nav-buttons { - white-space: nowrap; -} - -#nav-buttons button { - background: #fff; - border: 0; - border-radius: 10px; -} - -#nav-buttons button:active { - -webkit-box-shadow: 0 0 5px #33f inset; - background: #aaa; -} - -#nav-buttons button[disabled] { - opacity: .5; -} - -#controls { - float: right; -} - -#test-output { - border-spacing: 0; - border-collapse: collapse; - margin: 0 auto; - width: 100%; -} - -#test-output td, -#test-output th { - padding: 0; - vertical-align: top; -} - -#image-outputs img, -#image-outputs canvas, -#image-outputs #diff-checksum { - width: 800px; - height: 600px; - border: solid 1px #ddd; - -webkit-user-select: none; - -webkit-user-drag: none; -} - -#image-outputs img, -#image-outputs canvas { - cursor: crosshair; -} - -#image-outputs img.loading, -#image-outputs canvas.loading { - opacity: .5; -} - -#image-outputs #actual-image { - margin: 0 1em; -} - -#test-output #labels th { - text-align: center; - color: #666; -} - -#text-outputs pre { - height: 600px; - width: 800px; - overflow: auto; -} - -#test-output h2 { - border-bottom: solid 1px #ccc; - font-weight: bold; - margin: 0; - background: #eee; -} - -#footer { - position: absolute; - bottom: 0; - left: 0; - right: 0; - margin-top: 1em; -} - -#state.needs_rebaseline { - color: yellow; -} - -#state.rebaseline_failed { - color: red; -} - -#state.rebaseline_succeeded { - color: green; -} - -#state.in_queue { - color: gray; -} - -#action-buttons { - float: right; -} - -#action-buttons .link { - margin-right: 1em; -} - -#footer button { - padding: 1em; -} - -#loupe { - -webkit-box-shadow: 2px 2px 5px rgba(0, 0, 0, .5); - position: absolute; - width: 634px; - top: 50%; - left: 50%; - margin-left: -151px; - margin-top: -50px; - background: #fff; - border-spacing: 0; - border-collapse: collapse; -} - -#loupe td { - padding: 0; - border: solid 1px #ccc; -} - -#loupe label { - color: #999; - padding-right: 1em; -} - -#loupe span { - color: #000; - font-weight: bold; -} - -#loupe canvas { - cursor: crosshair; -} - -#loupe #loupe-close { - float: right; -} - -#loupe #loupe-info { - background: #eee; - padding: .3em .5em; -} - -#loupe #loupe-colors td { - text-align: center; -} - -#loupe .loupe-container { - position: relative; - width: 210px; - height: 210px; -} - -#loupe .center-highlight { - position: absolute; - width: 10px; - height: 10px; - top: 50%; - left: 50%; - margin-left: -5px; - margin-top: -5px; - outline: solid 1px #999; -} diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js deleted file mode 100644 index 25f9146..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js +++ /dev/null @@ -1,498 +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. - */ - -var ALL_DIRECTORY_PATH = '[all]'; - -var STATE_NEEDS_REBASELINE = 'needs_rebaseline'; -var STATE_REBASELINE_FAILED = 'rebaseline_failed'; -var STATE_REBASELINE_SUCCEEDED = 'rebaseline_succeeded'; -var STATE_IN_QUEUE = 'in_queue'; -var STATE_TO_DISPLAY_STATE = {}; -STATE_TO_DISPLAY_STATE[STATE_NEEDS_REBASELINE] = 'Needs rebaseline'; -STATE_TO_DISPLAY_STATE[STATE_REBASELINE_FAILED] = 'Rebaseline failed'; -STATE_TO_DISPLAY_STATE[STATE_REBASELINE_SUCCEEDED] = 'Rebaseline succeeded'; -STATE_TO_DISPLAY_STATE[STATE_IN_QUEUE] = 'In queue'; - -var results; -var testsByFailureType = {}; -var testsByDirectory = {}; -var selectedTests = []; -var loupe; -var queue; - -function main() -{ - $('failure-type-selector').addEventListener('change', selectFailureType); - $('directory-selector').addEventListener('change', selectDirectory); - $('test-selector').addEventListener('change', selectTest); - $('next-test').addEventListener('click', nextTest); - $('previous-test').addEventListener('click', previousTest); - - $('toggle-log').addEventListener('click', function() { toggle('log'); }); - - loupe = new Loupe(); - queue = new RebaselineQueue(); - - document.addEventListener('keydown', function(event) { - if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { - return; - } - - switch (event.keyIdentifier) { - case 'Left': - event.preventDefault(); - previousTest(); - break; - case 'Right': - event.preventDefault(); - nextTest(); - break; - case 'U+0051': // q - queue.addCurrentTest(); - break; - case 'U+0058': // x - queue.removeCurrentTest(); - break; - case 'U+0052': // r - queue.rebaseline(); - break; - } - }); - - loadText('/platforms.json', function(text) { - var platforms = JSON.parse(text); - platforms.platforms.forEach(function(platform) { - var platformOption = document.createElement('option'); - platformOption.value = platform; - platformOption.textContent = platform; - - var targetOption = platformOption.cloneNode(true); - targetOption.selected = platform == platforms.defaultPlatform; - $('baseline-target').appendChild(targetOption); - $('baseline-move-to').appendChild(platformOption.cloneNode(true)); - }); - }); - - loadText('/results.json', function(text) { - results = JSON.parse(text); - displayResults(); - }); -} - -/** - * Groups test results by failure type. - */ -function displayResults() -{ - var failureTypeSelector = $('failure-type-selector'); - var failureTypes = []; - - for (var testName in results.tests) { - var test = results.tests[testName]; - if (test.actual == 'PASS') { - continue; - } - var failureType = test.actual + ' (expected ' + test.expected + ')'; - if (!(failureType in testsByFailureType)) { - testsByFailureType[failureType] = []; - failureTypes.push(failureType); - } - testsByFailureType[failureType].push(testName); - } - - // Sort by number of failures - failureTypes.sort(function(a, b) { - return testsByFailureType[b].length - testsByFailureType[a].length; - }); - - for (var i = 0, failureType; failureType = failureTypes[i]; i++) { - var failureTypeOption = document.createElement('option'); - failureTypeOption.value = failureType; - failureTypeOption.textContent = failureType + ' - ' + testsByFailureType[failureType].length + ' tests'; - failureTypeSelector.appendChild(failureTypeOption); - } - - selectFailureType(); - - document.body.classList.remove('loading'); -} - -/** - * For a given failure type, gets all the tests and groups them by directory - * (populating the directory selector with them). - */ -function selectFailureType() -{ - var selectedFailureType = getSelectValue('failure-type-selector'); - var tests = testsByFailureType[selectedFailureType]; - - testsByDirectory = {} - var displayDirectoryNamesByDirectory = {}; - var directories = []; - - // Include a special option for all tests - testsByDirectory[ALL_DIRECTORY_PATH] = tests; - displayDirectoryNamesByDirectory[ALL_DIRECTORY_PATH] = 'all'; - directories.push(ALL_DIRECTORY_PATH); - - // Roll up tests by ancestor directories - tests.forEach(function(test) { - var pathPieces = test.split('/'); - var pathDirectories = pathPieces.slice(0, pathPieces.length -1); - var ancestorDirectory = ''; - - pathDirectories.forEach(function(pathDirectory, index) { - ancestorDirectory += pathDirectory + '/'; - if (!(ancestorDirectory in testsByDirectory)) { - testsByDirectory[ancestorDirectory] = []; - var displayDirectoryName = new Array(index * 6).join(' ') + pathDirectory; - displayDirectoryNamesByDirectory[ancestorDirectory] = displayDirectoryName; - directories.push(ancestorDirectory); - } - - testsByDirectory[ancestorDirectory].push(test); - }); - }); - - directories.sort(); - - var directorySelector = $('directory-selector'); - directorySelector.innerHTML = ''; - - directories.forEach(function(directory) { - var directoryOption = document.createElement('option'); - directoryOption.value = directory; - directoryOption.innerHTML = - displayDirectoryNamesByDirectory[directory] + ' - ' + - testsByDirectory[directory].length + ' tests'; - directorySelector.appendChild(directoryOption); - }); - - selectDirectory(); -} - -/** - * For a given failure type and directory and failure type, gets all the tests - * in that directory and populatest the test selector with them. - */ -function selectDirectory() -{ - var selectedDirectory = getSelectValue('directory-selector'); - selectedTests = testsByDirectory[selectedDirectory]; - - selectedTests.sort(); - - var testSelector = $('test-selector'); - testSelector.innerHTML = ''; - - selectedTests.forEach(function(testName) { - var testOption = document.createElement('option'); - testOption.value = testName; - var testDisplayName = testName; - if (testName.lastIndexOf(selectedDirectory) == 0) { - testDisplayName = testName.substring(selectedDirectory.length); - } - testOption.innerHTML = ' ' + testDisplayName; - testSelector.appendChild(testOption); - }); - - selectTest(); -} - -function getSelectedTest() -{ - return getSelectValue('test-selector'); -} - -function selectTest() -{ - var selectedTest = getSelectedTest(); - - if (results.tests[selectedTest].actual.indexOf('IMAGE') != -1) { - $('image-outputs').style.display = ''; - displayImageResults(selectedTest); - } else { - $('image-outputs').style.display = 'none'; - } - - if (results.tests[selectedTest].actual.indexOf('TEXT') != -1) { - $('text-outputs').style.display = ''; - displayTextResults(selectedTest); - } else { - $('text-outputs').style.display = 'none'; - } - - var currentBaselines = $('current-baselines'); - currentBaselines.textContent = ''; - var baselines = results.tests[selectedTest].baselines; - var testName = selectedTest.split('.').slice(0, -1).join('.'); - for (var platform in baselines) { - if (currentBaselines.firstChild) { - currentBaselines.appendChild(document.createTextNode('; ')); - } - currentBaselines.appendChild(document.createTextNode(platform + ' (')); - for (var i = 0, extension; extension = baselines[platform][i]; i++) { - if (i != 0) { - currentBaselines.appendChild(document.createTextNode(', ')); - } - var link = document.createElement('a'); - var baselinePath = ''; - if (platform != 'base') { - baselinePath += 'platform/' + platform + '/'; - } - baselinePath += testName + '-expected' + extension; - link.href = getTracUrl(baselinePath); - if (extension == '.checksum') { - link.textContent = 'chk'; - } else { - link.textContent = extension.substring(1); - } - link.target = '_blank'; - currentBaselines.appendChild(link); - } - currentBaselines.appendChild(document.createTextNode(')')); - } - - updateState(); - loupe.hide(); - - prefetchNextImageTest(); -} - -function prefetchNextImageTest() -{ - var testSelector = $('test-selector'); - if (testSelector.selectedIndex == testSelector.options.length - 1) { - return; - } - var nextTest = testSelector.options[testSelector.selectedIndex + 1].value; - if (results.tests[nextTest].actual.indexOf('IMAGE') != -1) { - new Image().src = getTestResultUrl(nextTest, 'expected-image'); - new Image().src = getTestResultUrl(nextTest, 'actual-image'); - } -} - -function updateState() -{ - var testName = getSelectedTest(); - var testIndex = selectedTests.indexOf(testName); - var testCount = selectedTests.length - $('test-index').textContent = testIndex + 1; - $('test-count').textContent = testCount; - - $('next-test').disabled = testIndex == testCount - 1; - $('previous-test').disabled = testIndex == 0; - - $('test-link').href = getTracUrl(testName); - - var state = results.tests[testName].state; - $('state').className = state; - $('state').innerHTML = STATE_TO_DISPLAY_STATE[state]; - - queue.updateState(); -} - -function getTestResultUrl(testName, mode) -{ - return '/test_result?test=' + testName + '&mode=' + mode; -} - -var currentExpectedImageTest; -var currentActualImageTest; - -function displayImageResults(testName) -{ - if (currentExpectedImageTest == currentActualImageTest - && currentExpectedImageTest == testName) { - return; - } - - function displayImageResult(mode, callback) { - var image = $(mode); - image.className = 'loading'; - image.src = getTestResultUrl(testName, mode); - image.onload = function() { - image.className = ''; - callback(); - updateImageDiff(); - }; - } - - displayImageResult( - 'expected-image', - function() { currentExpectedImageTest = testName; }); - displayImageResult( - 'actual-image', - function() { currentActualImageTest = testName; }); - - $('diff-canvas').className = 'loading'; - $('diff-canvas').style.display = ''; - $('diff-checksum').style.display = 'none'; -} - -/** - * Computes a graphical a diff between the expected and actual images by - * rendering each to a canvas, getting the image data, and comparing the RGBA - * components of each pixel. The output is put into the diff canvas, with - * identical pixels appearing at 12.5% opacity and different pixels being - * highlighted in red. - */ -function updateImageDiff() { - if (currentExpectedImageTest != currentActualImageTest) - return; - - var expectedImage = $('expected-image'); - var actualImage = $('actual-image'); - - function getImageData(image) { - var imageCanvas = document.createElement('canvas'); - imageCanvas.width = image.width; - imageCanvas.height = image.height; - imageCanvasContext = imageCanvas.getContext('2d'); - - imageCanvasContext.fillStyle = 'rgba(255, 255, 255, 1)'; - imageCanvasContext.fillRect( - 0, 0, image.width, image.height); - - imageCanvasContext.drawImage(image, 0, 0); - return imageCanvasContext.getImageData( - 0, 0, image.width, image.height); - } - - var expectedImageData = getImageData(expectedImage); - var actualImageData = getImageData(actualImage); - - var diffCanvas = $('diff-canvas'); - var diffCanvasContext = diffCanvas.getContext('2d'); - var diffImageData = - diffCanvasContext.createImageData(diffCanvas.width, diffCanvas.height); - - // Avoiding property lookups for all these during the per-pixel loop below - // provides a significant performance benefit. - var expectedWidth = expectedImage.width; - var expectedHeight = expectedImage.height; - var expected = expectedImageData.data; - - var actualWidth = actualImage.width; - var actual = actualImageData.data; - - var diffWidth = diffImageData.width; - var diff = diffImageData.data; - - var hadDiff = false; - for (var x = 0; x < expectedWidth; x++) { - for (var y = 0; y < expectedHeight; y++) { - var expectedOffset = (y * expectedWidth + x) * 4; - var actualOffset = (y * actualWidth + x) * 4; - var diffOffset = (y * diffWidth + x) * 4; - if (expected[expectedOffset] != actual[actualOffset] || - expected[expectedOffset + 1] != actual[actualOffset + 1] || - expected[expectedOffset + 2] != actual[actualOffset + 2] || - expected[expectedOffset + 3] != actual[actualOffset + 3]) { - hadDiff = true; - diff[diffOffset] = 255; - diff[diffOffset + 1] = 0; - diff[diffOffset + 2] = 0; - diff[diffOffset + 3] = 255; - } else { - diff[diffOffset] = expected[expectedOffset]; - diff[diffOffset + 1] = expected[expectedOffset + 1]; - diff[diffOffset + 2] = expected[expectedOffset + 2]; - diff[diffOffset + 3] = 32; - } - } - } - - diffCanvasContext.putImageData( - diffImageData, - 0, 0, - 0, 0, - diffImageData.width, diffImageData.height); - diffCanvas.className = ''; - - if (!hadDiff) { - diffCanvas.style.display = 'none'; - $('diff-checksum').style.display = ''; - loadTextResult(currentExpectedImageTest, 'expected-checksum'); - loadTextResult(currentExpectedImageTest, 'actual-checksum'); - } -} - -function loadTextResult(testName, mode) -{ - loadText(getTestResultUrl(testName, mode), function(text) { - $(mode).textContent = text; - }); -} - -function displayTextResults(testName) -{ - loadTextResult(testName, 'expected-text'); - loadTextResult(testName, 'actual-text'); - loadTextResult(testName, 'diff-text'); -} - -function nextTest() -{ - var testSelector = $('test-selector'); - var nextTestIndex = testSelector.selectedIndex + 1; - while (true) { - if (nextTestIndex == testSelector.options.length) { - return; - } - if (testSelector.options[nextTestIndex].disabled) { - nextTestIndex++; - } else { - testSelector.selectedIndex = nextTestIndex; - selectTest(); - return; - } - } -} - -function previousTest() -{ - var testSelector = $('test-selector'); - var previousTestIndex = testSelector.selectedIndex - 1; - while (true) { - if (previousTestIndex == -1) { - return; - } - if (testSelector.options[previousTestIndex].disabled) { - previousTestIndex--; - } else { - testSelector.selectedIndex = previousTestIndex; - selectTest(); - return - } - } -} - -window.addEventListener('DOMContentLoaded', main); diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/queue.js b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/queue.js deleted file mode 100644 index f57c919..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/queue.js +++ /dev/null @@ -1,158 +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. - */ - -function RebaselineQueue() -{ - this._selectNode = $('queue-select'); - this._rebaselineButtonNode = $('rebaseline-queue'); - this._toggleNode = $('toggle-queue'); - this._removeSelectionButtonNode = $('remove-queue-selection'); - - this._inProgressRebaselineCount = 0; - - var self = this; - $('add-to-rebaseline-queue').addEventListener( - 'click', function() { self.addCurentTest(); }); - this._selectNode.addEventListener('change', updateState); - this._removeSelectionButtonNode.addEventListener( - 'click', function() { self._removeSelection(); }); - this._rebaselineButtonNode.addEventListener( - 'click', function() { self.rebaseline(); }); - this._toggleNode.addEventListener( - 'click', function() { toggle('queue'); }); -} - -RebaselineQueue.prototype.updateState = function() -{ - var testName = getSelectedTest(); - - var state = results.tests[testName].state; - $('add-to-rebaseline-queue').disabled = state != STATE_NEEDS_REBASELINE; - - var queueLength = this._selectNode.options.length; - if (this._inProgressRebaselineCount > 0) { - this._rebaselineButtonNode.disabled = true; - this._rebaselineButtonNode.textContent = - 'Rebaseline in progress (' + this._inProgressRebaselineCount + - ' tests left)'; - } else if (queueLength == 0) { - this._rebaselineButtonNode.disabled = true; - this._rebaselineButtonNode.textContent = 'Rebaseline queue'; - this._toggleNode.textContent = 'Queue'; - } else { - this._rebaselineButtonNode.disabled = false; - this._rebaselineButtonNode.textContent = - 'Rebaseline queue (' + queueLength + ' tests)'; - this._toggleNode.textContent = 'Queue (' + queueLength + ' tests)'; - } - this._removeSelectionButtonNode.disabled = - this._selectNode.selectedIndex == -1; -}; - -RebaselineQueue.prototype.addCurrentTest = function() -{ - var testName = getSelectedTest(); - var test = results.tests[testName]; - - if (test.state != STATE_NEEDS_REBASELINE) { - log('Cannot add test with state "' + test.state + '" to queue.', - log.WARNING); - return; - } - - var queueOption = document.createElement('option'); - queueOption.value = testName; - queueOption.textContent = testName; - this._selectNode.appendChild(queueOption); - test.state = STATE_IN_QUEUE; - updateState(); -}; - -RebaselineQueue.prototype.removeCurrentTest = function() -{ - this._removeTest(getSelectedTest()); -}; - -RebaselineQueue.prototype._removeSelection = function() -{ - if (this._selectNode.selectedIndex == -1) - return; - - this._removeTest( - this._selectNode.options[this._selectNode.selectedIndex].value); -}; - -RebaselineQueue.prototype._removeTest = function(testName) -{ - var queueOption = this._selectNode.firstChild; - - while (queueOption && queueOption.value != testName) { - queueOption = queueOption.nextSibling; - } - - if (!queueOption) - return; - - this._selectNode.removeChild(queueOption); - var test = results.tests[testName]; - test.state = STATE_NEEDS_REBASELINE; - updateState(); -}; - -RebaselineQueue.prototype.rebaseline = function() -{ - var testNames = []; - for (var queueOption = this._selectNode.firstChild; - queueOption; - queueOption = queueOption.nextSibling) { - testNames.push(queueOption.value); - } - - this._inProgressRebaselineCount = testNames.length; - updateState(); - - testNames.forEach(this._rebaselineTest, this); -}; - -RebaselineQueue.prototype._rebaselineTest = function(testName) -{ - var baselineTarget = getSelectValue('baseline-target'); - var baselineMoveTo = getSelectValue('baseline-move-to'); - - // FIXME: actually rebaseline - log('Rebaselining ' + testName + ' for platform ' + baselineTarget + '...'); - var test = results.tests[testName]; - this._removeTest(testName); - this._inProgressRebaselineCount--; - test.state = STATE_REBASELINE_SUCCEEDED; - updateState(); - log('Rebaselined add test with state ' + test.state + ' to queue', - log.SUCCESS); -}; diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/util.js b/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/util.js deleted file mode 100644 index b348462..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/data/rebaselineserver/util.js +++ /dev/null @@ -1,94 +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. - */ - -var results; -var testsByFailureType = {}; -var testsByDirectory = {}; -var selectedTests = []; - -function $(id) -{ - return document.getElementById(id); -} - -function getSelectValue(id) -{ - var select = $(id); - if (select.selectedIndex == -1) { - return null; - } else { - return select.options[select.selectedIndex].value; - } -} - -function loadText(url, callback) -{ - var xhr = new XMLHttpRequest(); - xhr.open('GET', url); - xhr.addEventListener('load', function() { callback(xhr.responseText); }); - xhr.send(); -} - -function log(text, type) -{ - var node = $('log'); - - if (type) { - var typeNode = document.createElement('span'); - typeNode.textContent = type.text; - typeNode.style.color = type.color; - node.appendChild(typeNode); - } - - node.appendChild(document.createTextNode(text + '\n')); - node.scrollTop = node.scrollHeight; -} - -log.WARNING = {text: 'Warning: ', color: '#aa3'}; -log.SUCCESS = {text: 'Success: ', color: 'green'}; -log.ERROR = {text: 'Error: ', color: 'red'}; - -function toggle(id) -{ - var element = $(id); - var toggler = $('toggle-' + id); - if (element.style.display == 'none') { - element.style.display = ''; - toggler.className = 'link selected'; - } else { - element.style.display = 'none'; - toggler.className = 'link'; - } -} - -function getTracUrl(layoutTestPath) -{ - return 'http://trac.webkit.org/browser/trunk/LayoutTests/' + layoutTestPath; -}
\ No newline at end of file diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/download.py b/WebKitTools/Scripts/webkitpy/tool/commands/download.py deleted file mode 100644 index 457c050..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/download.py +++ /dev/null @@ -1,394 +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. - -import os - -import webkitpy.tool.steps as steps - -from webkitpy.common.checkout.changelog import ChangeLog, view_source_url -from webkitpy.common.system.executive import ScriptError -from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand -from webkitpy.tool.commands.stepsequence import StepSequence -from webkitpy.tool.comments import bug_comment_from_commit_text -from webkitpy.tool.grammar import pluralize -from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand -from webkitpy.common.system.deprecated_logging import error, log - - -class Clean(AbstractSequencedCommand): - name = "clean" - help_text = "Clean the working copy" - steps = [ - steps.CleanWorkingDirectory, - ] - - def _prepare_state(self, options, args, tool): - options.force_clean = True - - -class Update(AbstractSequencedCommand): - name = "update" - help_text = "Update working copy (used internally)" - steps = [ - steps.CleanWorkingDirectory, - steps.Update, - ] - - -class Build(AbstractSequencedCommand): - name = "build" - help_text = "Update working copy and build" - steps = [ - steps.CleanWorkingDirectory, - steps.Update, - steps.Build, - ] - - def _prepare_state(self, options, args, tool): - options.build = True - - -class BuildAndTest(AbstractSequencedCommand): - name = "build-and-test" - help_text = "Update working copy, build, and run the tests" - steps = [ - steps.CleanWorkingDirectory, - steps.Update, - steps.Build, - steps.RunTests, - ] - - -class Land(AbstractSequencedCommand): - name = "land" - help_text = "Land the current working directory diff and updates the associated bug if any" - argument_names = "[BUGID]" - show_in_main_help = True - steps = [ - steps.EnsureBuildersAreGreen, - steps.UpdateChangeLogsWithReviewer, - steps.ValidateReviewer, - steps.Build, - steps.RunTests, - steps.Commit, - steps.CloseBugForLandDiff, - ] - long_help = """land commits the current working copy diff (just as svn or git commit would). -land will NOT build and run the tests before committing, but you can use the --build option for that. -If a bug id is provided, or one can be found in the ChangeLog land will update the bug after committing.""" - - def _prepare_state(self, options, args, tool): - changed_files = self._tool.scm().changed_files(options.git_commit) - return { - "changed_files": changed_files, - "bug_id": (args and args[0]) or tool.checkout().bug_id_for_this_commit(options.git_commit, changed_files), - } - - -class LandCowboy(AbstractSequencedCommand): - name = "land-cowboy" - help_text = "Prepares a ChangeLog and lands the current working directory diff." - steps = [ - steps.PrepareChangeLog, - steps.EditChangeLog, - steps.ConfirmDiff, - steps.Build, - steps.RunTests, - steps.Commit, - ] - - -class AbstractPatchProcessingCommand(AbstractDeclarativeCommand): - # Subclasses must implement the methods below. We don't declare them here - # because we want to be able to implement them with mix-ins. - # - # def _fetch_list_of_patches_to_process(self, options, args, tool): - # def _prepare_to_process(self, options, args, tool): - - @staticmethod - def _collect_patches_by_bug(patches): - bugs_to_patches = {} - for patch in patches: - bugs_to_patches[patch.bug_id()] = bugs_to_patches.get(patch.bug_id(), []) + [patch] - return bugs_to_patches - - def execute(self, options, args, tool): - self._prepare_to_process(options, args, tool) - patches = self._fetch_list_of_patches_to_process(options, args, tool) - - # It's nice to print out total statistics. - bugs_to_patches = self._collect_patches_by_bug(patches) - log("Processing %s from %s." % (pluralize("patch", len(patches)), pluralize("bug", len(bugs_to_patches)))) - - for patch in patches: - self._process_patch(patch, options, args, tool) - - -class AbstractPatchSequencingCommand(AbstractPatchProcessingCommand): - prepare_steps = None - main_steps = None - - def __init__(self): - options = [] - self._prepare_sequence = StepSequence(self.prepare_steps) - self._main_sequence = StepSequence(self.main_steps) - options = sorted(set(self._prepare_sequence.options() + self._main_sequence.options())) - AbstractPatchProcessingCommand.__init__(self, options) - - def _prepare_to_process(self, options, args, tool): - self._prepare_sequence.run_and_handle_errors(tool, options) - - def _process_patch(self, patch, options, args, tool): - state = { "patch" : patch } - self._main_sequence.run_and_handle_errors(tool, options, state) - - -class ProcessAttachmentsMixin(object): - def _fetch_list_of_patches_to_process(self, options, args, tool): - return map(lambda patch_id: tool.bugs.fetch_attachment(patch_id), args) - - -class ProcessBugsMixin(object): - def _fetch_list_of_patches_to_process(self, options, args, tool): - all_patches = [] - for bug_id in args: - patches = tool.bugs.fetch_bug(bug_id).reviewed_patches() - log("%s found on bug %s." % (pluralize("reviewed patch", len(patches)), bug_id)) - all_patches += patches - return all_patches - - -class CheckStyle(AbstractPatchSequencingCommand, ProcessAttachmentsMixin): - name = "check-style" - help_text = "Run check-webkit-style on the specified attachments" - argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]" - main_steps = [ - steps.CleanWorkingDirectory, - steps.Update, - steps.ApplyPatch, - steps.CheckStyle, - ] - - -class BuildAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsMixin): - name = "build-attachment" - help_text = "Apply and build patches from bugzilla" - argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]" - main_steps = [ - steps.CleanWorkingDirectory, - steps.Update, - steps.ApplyPatch, - steps.Build, - ] - - -class BuildAndTestAttachment(AbstractPatchSequencingCommand, ProcessAttachmentsMixin): - name = "build-and-test-attachment" - help_text = "Apply, build, and test patches from bugzilla" - argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]" - main_steps = [ - steps.CleanWorkingDirectory, - steps.Update, - steps.ApplyPatch, - steps.Build, - steps.RunTests, - ] - - -class AbstractPatchApplyingCommand(AbstractPatchSequencingCommand): - prepare_steps = [ - steps.EnsureLocalCommitIfNeeded, - steps.CleanWorkingDirectoryWithLocalCommits, - steps.Update, - ] - main_steps = [ - steps.ApplyPatchWithLocalCommit, - ] - long_help = """Updates the working copy. -Downloads and applies the patches, creating local commits if necessary.""" - - -class ApplyAttachment(AbstractPatchApplyingCommand, ProcessAttachmentsMixin): - name = "apply-attachment" - help_text = "Apply an attachment to the local working directory" - argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]" - show_in_main_help = True - - -class ApplyFromBug(AbstractPatchApplyingCommand, ProcessBugsMixin): - name = "apply-from-bug" - help_text = "Apply reviewed patches from provided bugs to the local working directory" - argument_names = "BUGID [BUGIDS]" - show_in_main_help = True - - -class AbstractPatchLandingCommand(AbstractPatchSequencingCommand): - prepare_steps = [ - steps.EnsureBuildersAreGreen, - ] - main_steps = [ - steps.CleanWorkingDirectory, - steps.Update, - steps.ApplyPatch, - steps.ValidateReviewer, - steps.Build, - steps.RunTests, - steps.Commit, - steps.ClosePatch, - steps.CloseBug, - ] - long_help = """Checks to make sure builders are green. -Updates the working copy. -Applies the patch. -Builds. -Runs the layout tests. -Commits the patch. -Clears the flags on the patch. -Closes the bug if no patches are marked for review.""" - - -class LandAttachment(AbstractPatchLandingCommand, ProcessAttachmentsMixin): - name = "land-attachment" - help_text = "Land patches from bugzilla, optionally building and testing them first" - argument_names = "ATTACHMENT_ID [ATTACHMENT_IDS]" - show_in_main_help = True - - -class LandFromBug(AbstractPatchLandingCommand, ProcessBugsMixin): - name = "land-from-bug" - help_text = "Land all patches on the given bugs, optionally building and testing them first" - argument_names = "BUGID [BUGIDS]" - show_in_main_help = True - - -class AbstractRolloutPrepCommand(AbstractSequencedCommand): - argument_names = "REVISION REASON" - - def _commit_info(self, revision): - commit_info = self._tool.checkout().commit_info_for_revision(revision) - if commit_info and commit_info.bug_id(): - # Note: Don't print a bug URL here because it will confuse the - # SheriffBot because the SheriffBot just greps the output - # of create-rollout for bug URLs. It should do better - # parsing instead. - log("Preparing rollout for bug %s." % commit_info.bug_id()) - else: - log("Unable to parse bug number from diff.") - return commit_info - - def _prepare_state(self, options, args, tool): - revision = args[0] - commit_info = self._commit_info(revision) - cc_list = sorted([party.bugzilla_email() - for party in commit_info.responsible_parties() - if party.bugzilla_email()]) - return { - "revision": revision, - "bug_id": commit_info.bug_id(), - # FIXME: We should used the list as the canonical representation. - "bug_cc": ",".join(cc_list), - "reason": args[1], - } - - -class PrepareRollout(AbstractRolloutPrepCommand): - name = "prepare-rollout" - help_text = "Revert the given revision in the working copy and prepare ChangeLogs with revert reason" - long_help = """Updates the working copy. -Applies the inverse diff for the provided revision. -Creates an appropriate rollout ChangeLog, including a trac link and bug link. -""" - steps = [ - steps.CleanWorkingDirectory, - steps.Update, - steps.RevertRevision, - steps.PrepareChangeLogForRevert, - ] - - -class CreateRollout(AbstractRolloutPrepCommand): - name = "create-rollout" - help_text = "Creates a bug to track a broken SVN revision and uploads a rollout patch." - steps = [ - steps.CleanWorkingDirectory, - steps.Update, - steps.RevertRevision, - steps.CreateBug, - steps.PrepareChangeLogForRevert, - steps.PostDiffForRevert, - ] - - def _prepare_state(self, options, args, tool): - state = AbstractRolloutPrepCommand._prepare_state(self, options, args, tool) - # Currently, state["bug_id"] points to the bug that caused the - # regression. We want to create a new bug that blocks the old bug - # so we move state["bug_id"] to state["bug_blocked"] and delete the - # old state["bug_id"] so that steps.CreateBug will actually create - # the new bug that we want (and subsequently store its bug id into - # state["bug_id"]) - state["bug_blocked"] = state["bug_id"] - del state["bug_id"] - state["bug_title"] = "REGRESSION(r%s): %s" % (state["revision"], state["reason"]) - state["bug_description"] = "%s broke the build:\n%s" % (view_source_url(state["revision"]), state["reason"]) - # FIXME: If we had more context here, we could link to other open bugs - # that mention the test that regressed. - if options.parent_command == "sheriff-bot": - state["bug_description"] += """ - -This is an automatic bug report generated by the sheriff-bot. If this bug -report was created because of a flaky test, please file a bug for the flaky -test (if we don't already have one on file) and dup this bug against that bug -so that we can track how often these flaky tests case pain. - -"Only you can prevent forest fires." -- Smokey the Bear -""" - return state - - -class Rollout(AbstractRolloutPrepCommand): - name = "rollout" - show_in_main_help = True - help_text = "Revert the given revision in the working copy and optionally commit the revert and re-open the original bug" - long_help = """Updates the working copy. -Applies the inverse diff for the provided revision. -Creates an appropriate rollout ChangeLog, including a trac link and bug link. -Opens the generated ChangeLogs in $EDITOR. -Shows the prepared diff for confirmation. -Commits the revert and updates the bug (including re-opening the bug if necessary).""" - steps = [ - steps.CleanWorkingDirectory, - steps.Update, - steps.RevertRevision, - steps.PrepareChangeLogForRevert, - steps.EditChangeLog, - steps.ConfirmDiff, - steps.Build, - steps.Commit, - steps.ReopenBugAfterRollout, - ] diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/download_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/download_unittest.py deleted file mode 100644 index 9ca343b..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/download_unittest.py +++ /dev/null @@ -1,190 +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.system.outputcapture import OutputCapture -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.commands.commandtest import CommandsTest -from webkitpy.tool.commands.download import * -from webkitpy.tool.mocktool import MockOptions, MockTool - - -class AbstractRolloutPrepCommandTest(unittest.TestCase): - def test_commit_info(self): - command = AbstractRolloutPrepCommand() - tool = MockTool() - command.bind_to_tool(tool) - output = OutputCapture() - - expected_stderr = "Preparing rollout for bug 42.\n" - commit_info = output.assert_outputs(self, command._commit_info, [1234], expected_stderr=expected_stderr) - self.assertTrue(commit_info) - - mock_commit_info = Mock() - mock_commit_info.bug_id = lambda: None - tool._checkout.commit_info_for_revision = lambda revision: mock_commit_info - expected_stderr = "Unable to parse bug number from diff.\n" - commit_info = output.assert_outputs(self, command._commit_info, [1234], expected_stderr=expected_stderr) - self.assertEqual(commit_info, mock_commit_info) - - -class DownloadCommandsTest(CommandsTest): - def _default_options(self): - options = MockOptions() - options.build = True - options.build_style = True - options.check_builders = True - options.check_style = True - options.clean = True - options.close_bug = True - options.force_clean = False - options.force_patch = True - options.non_interactive = False - options.parent_command = 'MOCK parent command' - options.quiet = False - options.test = True - options.update = True - return options - - def test_build(self): - expected_stderr = "Updating working directory\nBuilding WebKit\n" - self.assert_execute_outputs(Build(), [], options=self._default_options(), expected_stderr=expected_stderr) - - def test_build_and_test(self): - expected_stderr = "Updating working directory\nBuilding WebKit\nRunning Python unit tests\nRunning Perl unit tests\nRunning JavaScriptCore tests\nRunning run-webkit-tests\n" - self.assert_execute_outputs(BuildAndTest(), [], options=self._default_options(), expected_stderr=expected_stderr) - - def test_apply_attachment(self): - options = self._default_options() - options.update = True - options.local_commit = True - expected_stderr = "Updating working directory\nProcessing 1 patch from 1 bug.\nProcessing patch 197 from bug 42.\n" - self.assert_execute_outputs(ApplyAttachment(), [197], options=options, expected_stderr=expected_stderr) - - def test_apply_patches(self): - options = self._default_options() - options.update = True - options.local_commit = True - expected_stderr = "Updating working directory\n2 reviewed patches found on bug 42.\nProcessing 2 patches from 1 bug.\nProcessing patch 197 from bug 42.\nProcessing patch 128 from bug 42.\n" - self.assert_execute_outputs(ApplyFromBug(), [42], options=options, expected_stderr=expected_stderr) - - def test_land_diff(self): - expected_stderr = "Building WebKit\nRunning Python unit tests\nRunning Perl unit tests\nRunning JavaScriptCore tests\nRunning run-webkit-tests\nCommitted r49824: <http://trac.webkit.org/changeset/49824>\nUpdating bug 42\n" - mock_tool = MockTool() - mock_tool.scm().create_patch = Mock() - mock_tool.checkout().modified_changelogs = Mock(return_value=[]) - self.assert_execute_outputs(Land(), [42], options=self._default_options(), expected_stderr=expected_stderr, tool=mock_tool) - # Make sure we're not calling expensive calls too often. - self.assertEqual(mock_tool.scm().create_patch.call_count, 0) - self.assertEqual(mock_tool.checkout().modified_changelogs.call_count, 1) - - def test_land_red_builders(self): - expected_stderr = '\nWARNING: Builders ["Builder2"] are red, please watch your commit carefully.\nSee http://dummy_buildbot_host/console?category=core\n\nBuilding WebKit\nRunning Python unit tests\nRunning Perl unit tests\nRunning JavaScriptCore tests\nRunning run-webkit-tests\nCommitted r49824: <http://trac.webkit.org/changeset/49824>\nUpdating bug 42\n' - mock_tool = MockTool() - mock_tool.buildbot.light_tree_on_fire() - self.assert_execute_outputs(Land(), [42], options=self._default_options(), expected_stderr=expected_stderr, tool=mock_tool) - - def test_check_style(self): - expected_stderr = "Processing 1 patch from 1 bug.\nUpdating working directory\nProcessing patch 197 from bug 42.\nRunning check-webkit-style\n" - self.assert_execute_outputs(CheckStyle(), [197], options=self._default_options(), expected_stderr=expected_stderr) - - def test_build_attachment(self): - expected_stderr = "Processing 1 patch from 1 bug.\nUpdating working directory\nProcessing patch 197 from bug 42.\nBuilding WebKit\n" - self.assert_execute_outputs(BuildAttachment(), [197], options=self._default_options(), expected_stderr=expected_stderr) - - def test_land_attachment(self): - # FIXME: This expected result is imperfect, notice how it's seeing the same patch as still there after it thought it would have cleared the flags. - expected_stderr = """Processing 1 patch from 1 bug. -Updating working directory -Processing patch 197 from bug 42. -Building WebKit -Running Python unit tests -Running Perl unit tests -Running JavaScriptCore tests -Running run-webkit-tests -Committed r49824: <http://trac.webkit.org/changeset/49824> -Not closing bug 42 as attachment 197 has review=+. Assuming there are more patches to land from this bug. -""" - self.assert_execute_outputs(LandAttachment(), [197], options=self._default_options(), expected_stderr=expected_stderr) - - def test_land_patches(self): - # FIXME: This expected result is imperfect, notice how it's seeing the same patch as still there after it thought it would have cleared the flags. - expected_stderr = """2 reviewed patches found on bug 42. -Processing 2 patches from 1 bug. -Updating working directory -Processing patch 197 from bug 42. -Building WebKit -Running Python unit tests -Running Perl unit tests -Running JavaScriptCore tests -Running run-webkit-tests -Committed r49824: <http://trac.webkit.org/changeset/49824> -Not closing bug 42 as attachment 197 has review=+. Assuming there are more patches to land from this bug. -Updating working directory -Processing patch 128 from bug 42. -Building WebKit -Running Python unit tests -Running Perl unit tests -Running JavaScriptCore tests -Running run-webkit-tests -Committed r49824: <http://trac.webkit.org/changeset/49824> -Not closing bug 42 as attachment 197 has review=+. Assuming there are more patches to land from this bug. -""" - self.assert_execute_outputs(LandFromBug(), [42], options=self._default_options(), expected_stderr=expected_stderr) - - def test_prepare_rollout(self): - expected_stderr = "Preparing rollout for bug 42.\nUpdating working directory\nRunning prepare-ChangeLog\n" - self.assert_execute_outputs(PrepareRollout(), [852, "Reason"], options=self._default_options(), expected_stderr=expected_stderr) - - def test_create_rollout(self): - expected_stderr = """Preparing rollout for bug 42. -Updating working directory -MOCK create_bug -bug_title: REGRESSION(r852): Reason -bug_description: http://trac.webkit.org/changeset/852 broke the build: -Reason -Running prepare-ChangeLog -MOCK add_patch_to_bug: bug_id=None, description=ROLLOUT of r852, mark_for_review=False, mark_for_commit_queue=True, mark_for_landing=False --- Begin comment -- -Any committer can land this patch automatically by marking it commit-queue+. The commit-queue will build and test the patch before landing to ensure that the rollout will be successful. This process takes approximately 15 minutes. - -If you would like to land the rollout faster, you can use the following command: - - webkit-patch land-attachment ATTACHMENT_ID --ignore-builders - -where ATTACHMENT_ID is the ID of this attachment. --- End comment -- -""" - self.assert_execute_outputs(CreateRollout(), [852, "Reason"], options=self._default_options(), expected_stderr=expected_stderr) - - def test_rollout(self): - expected_stderr = "Preparing rollout for bug 42.\nUpdating working directory\nRunning prepare-ChangeLog\nMOCK: user.open_url: file://...\nBuilding WebKit\nCommitted r49824: <http://trac.webkit.org/changeset/49824>\n" - expected_stdout = "Was that diff correct?\n" - self.assert_execute_outputs(Rollout(), [852, "Reason"], options=self._default_options(), expected_stdout=expected_stdout, expected_stderr=expected_stderr) - diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem.py b/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem.py deleted file mode 100644 index 3b53d1a..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem.py +++ /dev/null @@ -1,182 +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.tool.commands.queues import AbstractReviewQueue -from webkitpy.common.config.committers import CommitterList -from webkitpy.common.config.ports import WebKitPort -from webkitpy.common.system.executive import ScriptError -from webkitpy.tool.bot.queueengine import QueueEngine - - -class AbstractEarlyWarningSystem(AbstractReviewQueue): - _build_style = "release" - - def __init__(self): - AbstractReviewQueue.__init__(self) - self.port = WebKitPort.port(self.port_name) - - def should_proceed_with_work_item(self, patch): - return True - - def _can_build(self): - try: - self.run_webkit_patch([ - "build", - self.port.flag(), - "--build-style=%s" % self._build_style, - "--force-clean", - "--no-update"]) - return True - except ScriptError, e: - failure_log = self._log_from_script_error_for_upload(e) - self._update_status("Unable to perform a build", results_file=failure_log) - return False - - def _build(self, patch, first_run=False): - try: - args = [ - "build-attachment", - self.port.flag(), - "--build", - "--build-style=%s" % self._build_style, - "--force-clean", - "--quiet", - "--non-interactive", - patch.id()] - if not first_run: - # See commit-queue for an explanation of what we're doing here. - args.append("--no-update") - args.append("--parent-command=%s" % self.name) - self.run_webkit_patch(args) - return True - except ScriptError, e: - if first_run: - return False - raise - - def review_patch(self, patch): - if patch.is_obsolete(): - self._did_error(patch, "%s does not process obsolete patches." % self.name) - return False - - if patch.bug().is_closed(): - self._did_error(patch, "%s does not process patches on closed bugs." % self.name) - return False - - if not self._build(patch, first_run=True): - if not self._can_build(): - return False - self._build(patch) - return True - - @classmethod - def handle_script_error(cls, tool, state, script_error): - is_svn_apply = script_error.command_name() == "svn-apply" - status_id = cls._update_status_for_script_error(tool, state, script_error, is_error=is_svn_apply) - if is_svn_apply: - QueueEngine.exit_after_handled_error(script_error) - results_link = tool.status_server.results_url_for_status(status_id) - message = "Attachment %s did not build on %s:\nBuild output: %s" % (state["patch"].id(), cls.port_name, results_link) - tool.bugs.post_comment_to_bug(state["patch"].bug_id(), message, cc=cls.watchers) - exit(1) - - -class GtkEWS(AbstractEarlyWarningSystem): - name = "gtk-ews" - port_name = "gtk" - watchers = AbstractEarlyWarningSystem.watchers + [ - "gns@gnome.org", - "xan.lopez@gmail.com", - ] - - -class EflEWS(AbstractEarlyWarningSystem): - name = "efl-ews" - port_name = "efl" - watchers = AbstractEarlyWarningSystem.watchers + [ - "leandro@profusion.mobi", - "antognolli@profusion.mobi", - "lucas.demarchi@profusion.mobi", - ] - - -class QtEWS(AbstractEarlyWarningSystem): - name = "qt-ews" - port_name = "qt" - - -class WinEWS(AbstractEarlyWarningSystem): - name = "win-ews" - port_name = "win" - # Use debug, the Apple Win port fails to link Release on 32-bit Windows. - # https://bugs.webkit.org/show_bug.cgi?id=39197 - _build_style = "debug" - - -class AbstractChromiumEWS(AbstractEarlyWarningSystem): - port_name = "chromium" - watchers = AbstractEarlyWarningSystem.watchers + [ - "dglazkov@chromium.org", - ] - - -class ChromiumLinuxEWS(AbstractChromiumEWS): - # FIXME: We should rename this command to cr-linux-ews, but that requires - # a database migration. :( - name = "chromium-ews" - - -class ChromiumWindowsEWS(AbstractChromiumEWS): - name = "cr-win-ews" - - -# For platforms that we can't run inside a VM (like Mac OS X), we require -# patches to be uploaded by committers, who are generally trustworthy folk. :) -class AbstractCommitterOnlyEWS(AbstractEarlyWarningSystem): - def __init__(self, committers=CommitterList()): - AbstractEarlyWarningSystem.__init__(self) - self._committers = committers - - def process_work_item(self, patch): - if not self._committers.committer_by_email(patch.attacher_email()): - self._did_error(patch, "%s cannot process patches from non-committers :(" % self.name) - return False - return AbstractEarlyWarningSystem.process_work_item(self, patch) - - -# FIXME: Inheriting from AbstractCommitterOnlyEWS is kinda a hack, but it -# happens to work because AbstractChromiumEWS and AbstractCommitterOnlyEWS -# provide disjoint sets of functionality, and Python is otherwise smart -# enough to handle the diamond inheritance. -class ChromiumMacEWS(AbstractChromiumEWS, AbstractCommitterOnlyEWS): - name = "cr-mac-ews" - - -class MacEWS(AbstractCommitterOnlyEWS): - name = "mac-ews" - port_name = "mac" diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py deleted file mode 100644 index 830e11c..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/earlywarningsystem_unittest.py +++ /dev/null @@ -1,132 +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 - -from webkitpy.thirdparty.mock import Mock -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.tool.bot.queueengine import QueueEngine -from webkitpy.tool.commands.earlywarningsystem import * -from webkitpy.tool.commands.queuestest import QueuesTest -from webkitpy.tool.mocktool import MockTool, MockOptions - - -class AbstractEarlyWarningSystemTest(QueuesTest): - def test_can_build(self): - # Needed to define port_name, used in AbstractEarlyWarningSystem.__init__ - class TestEWS(AbstractEarlyWarningSystem): - port_name = "win" # Needs to be a port which port/factory understands. - - queue = TestEWS() - queue.bind_to_tool(MockTool(log_executive=True)) - queue._options = MockOptions(port=None) - expected_stderr = "MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build', '--port=win', '--build-style=release', '--force-clean', '--no-update']\n" - OutputCapture().assert_outputs(self, queue._can_build, [], expected_stderr=expected_stderr) - - def mock_run_webkit_patch(args): - raise ScriptError("MOCK script error") - - queue.run_webkit_patch = mock_run_webkit_patch - expected_stderr = "MOCK: update_status: None Unable to perform a build\n" - OutputCapture().assert_outputs(self, queue._can_build, [], expected_stderr=expected_stderr) - - # FIXME: This belongs on an AbstractReviewQueueTest object in queues_unittest.py - def test_subprocess_handled_error(self): - queue = AbstractReviewQueue() - queue.bind_to_tool(MockTool()) - - def mock_review_patch(patch): - raise ScriptError('MOCK script error', exit_code=QueueEngine.handled_error_code) - - queue.review_patch = mock_review_patch - mock_patch = queue._tool.bugs.fetch_attachment(197) - expected_stderr = "MOCK: release_work_item: None 197\n" - OutputCapture().assert_outputs(self, queue.process_work_item, [mock_patch], expected_stderr=expected_stderr, expected_exception=ScriptError) - - -class EarlyWarningSytemTest(QueuesTest): - def test_failed_builds(self): - ews = ChromiumLinuxEWS() - ews.bind_to_tool(MockTool()) - ews._build = lambda patch, first_run=False: False - ews._can_build = lambda: True - mock_patch = ews._tool.bugs.fetch_attachment(197) - ews.review_patch(mock_patch) - - def _default_expected_stderr(self, ews): - string_replacemnts = { - "name": ews.name, - "port": ews.port_name, - "watchers": ews.watchers, - } - expected_stderr = { - "begin_work_queue": self._default_begin_work_queue_stderr(ews.name, ews._tool.scm().checkout_root), - "handle_unexpected_error": "Mock error message\n", - "next_work_item": "", - "process_work_item": "MOCK: update_status: %(name)s Pass\nMOCK: release_work_item: %(name)s 197\n" % string_replacemnts, - "handle_script_error": "MOCK: update_status: %(name)s ScriptError error message\nMOCK bug comment: bug_id=42, cc=%(watchers)s\n--- Begin comment ---\nAttachment 197 did not build on %(port)s:\nBuild output: http://dummy_url\n--- End comment ---\n\n" % string_replacemnts, - } - return expected_stderr - - def _test_ews(self, ews): - ews.bind_to_tool(MockTool()) - expected_exceptions = { - "handle_script_error": SystemExit, - } - self.assert_queue_outputs(ews, expected_stderr=self._default_expected_stderr(ews), expected_exceptions=expected_exceptions) - - def _test_committer_only_ews(self, ews): - ews.bind_to_tool(MockTool()) - expected_stderr = self._default_expected_stderr(ews) - string_replacemnts = {"name": ews.name} - expected_stderr["process_work_item"] = "MOCK: update_status: %(name)s Error: %(name)s cannot process patches from non-committers :(\nMOCK: release_work_item: %(name)s 197\n" % string_replacemnts - expected_exceptions = {"handle_script_error": SystemExit} - self.assert_queue_outputs(ews, expected_stderr=expected_stderr, expected_exceptions=expected_exceptions) - - # FIXME: If all EWSes are going to output the same text, we - # could test them all in one method with a for loop over an array. - def test_chromium_linux_ews(self): - self._test_ews(ChromiumLinuxEWS()) - - def test_chromium_windows_ews(self): - self._test_ews(ChromiumWindowsEWS()) - - def test_qt_ews(self): - self._test_ews(QtEWS()) - - def test_gtk_ews(self): - self._test_ews(GtkEWS()) - - def test_efl_ews(self): - self._test_ews(EflEWS()) - - def test_mac_ews(self): - self._test_committer_only_ews(MacEWS()) - - def test_chromium_mac_ews(self): - self._test_committer_only_ews(ChromiumMacEWS()) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/openbugs.py b/WebKitTools/Scripts/webkitpy/tool/commands/openbugs.py deleted file mode 100644 index 1b51c9f..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/openbugs.py +++ /dev/null @@ -1,63 +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 re -import sys - -from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand -from webkitpy.common.system.deprecated_logging import log - - -class OpenBugs(AbstractDeclarativeCommand): - name = "open-bugs" - help_text = "Finds all bug numbers passed in arguments (or stdin if no args provided) and opens them in a web browser" - - bug_number_regexp = re.compile(r"\b\d{4,6}\b") - - def _open_bugs(self, bug_ids): - for bug_id in bug_ids: - bug_url = self._tool.bugs.bug_url_for_bug_id(bug_id) - self._tool.user.open_url(bug_url) - - # _find_bugs_in_string mostly exists for easy unit testing. - def _find_bugs_in_string(self, string): - return self.bug_number_regexp.findall(string) - - def _find_bugs_in_iterable(self, iterable): - return sum([self._find_bugs_in_string(string) for string in iterable], []) - - def execute(self, options, args, tool): - if args: - bug_ids = self._find_bugs_in_iterable(args) - else: - # This won't open bugs until stdin is closed but could be made to easily. That would just make unit testing slightly harder. - bug_ids = self._find_bugs_in_iterable(sys.stdin) - - log("%s bugs found in input." % len(bug_ids)) - - self._open_bugs(bug_ids) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/openbugs_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/openbugs_unittest.py deleted file mode 100644 index 40a6e1b..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/openbugs_unittest.py +++ /dev/null @@ -1,50 +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.tool.commands.commandtest import CommandsTest -from webkitpy.tool.commands.openbugs import OpenBugs - -class OpenBugsTest(CommandsTest): - - find_bugs_in_string_expectations = [ - ["123", []], - ["1234", ["1234"]], - ["12345", ["12345"]], - ["123456", ["123456"]], - ["1234567", []], - [" 123456 234567", ["123456", "234567"]], - ] - - def test_find_bugs_in_string(self): - openbugs = OpenBugs() - for expectation in self.find_bugs_in_string_expectations: - self.assertEquals(openbugs._find_bugs_in_string(expectation[0]), expectation[1]) - - def test_args_parsing(self): - expected_stderr = "2 bugs found in input.\nMOCK: user.open_url: http://example.com/12345\nMOCK: user.open_url: http://example.com/23456\n" - self.assert_execute_outputs(OpenBugs(), ["12345\n23456"], expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/prettydiff.py b/WebKitTools/Scripts/webkitpy/tool/commands/prettydiff.py deleted file mode 100644 index e3fc00c..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/prettydiff.py +++ /dev/null @@ -1,38 +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 webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand -import webkitpy.tool.steps as steps - - -class PrettyDiff(AbstractSequencedCommand): - name = "pretty-diff" - help_text = "Shows the pretty diff in the default browser" - steps = [ - steps.ConfirmDiff, - ] diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queries.py b/WebKitTools/Scripts/webkitpy/tool/commands/queries.py deleted file mode 100644 index 16ddc2c..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/queries.py +++ /dev/null @@ -1,393 +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 optparse import make_option - -import webkitpy.tool.steps as steps - -from webkitpy.common.checkout.commitinfo import CommitInfo -from webkitpy.common.config.committers import CommitterList -from webkitpy.common.net.buildbot import BuildBot -from webkitpy.common.net.regressionwindow import RegressionWindow -from webkitpy.common.system.user import User -from webkitpy.tool.grammar import pluralize -from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand -from webkitpy.common.system.deprecated_logging import log -from webkitpy.layout_tests import port - - -class SuggestReviewers(AbstractDeclarativeCommand): - name = "suggest-reviewers" - help_text = "Suggest reviewers for a patch based on recent changes to the modified files." - - def __init__(self): - options = [ - steps.Options.git_commit, - ] - AbstractDeclarativeCommand.__init__(self, options=options) - - def execute(self, options, args, tool): - reviewers = tool.checkout().suggested_reviewers(options.git_commit) - print "\n".join([reviewer.full_name for reviewer in reviewers]) - - -class BugsToCommit(AbstractDeclarativeCommand): - name = "bugs-to-commit" - help_text = "List bugs in the commit-queue" - - def execute(self, options, args, tool): - # FIXME: This command is poorly named. It's fetching the commit-queue list here. The name implies it's fetching pending-commit (all r+'d patches). - bug_ids = tool.bugs.queries.fetch_bug_ids_from_commit_queue() - for bug_id in bug_ids: - print "%s" % bug_id - - -class PatchesInCommitQueue(AbstractDeclarativeCommand): - name = "patches-in-commit-queue" - help_text = "List patches in the commit-queue" - - def execute(self, options, args, tool): - patches = tool.bugs.queries.fetch_patches_from_commit_queue() - log("Patches in commit queue:") - for patch in patches: - print patch.url() - - -class PatchesToCommitQueue(AbstractDeclarativeCommand): - name = "patches-to-commit-queue" - help_text = "Patches which should be added to the commit queue" - def __init__(self): - options = [ - make_option("--bugs", action="store_true", dest="bugs", help="Output bug links instead of patch links"), - ] - AbstractDeclarativeCommand.__init__(self, options=options) - - @staticmethod - def _needs_commit_queue(patch): - if patch.commit_queue() == "+": # If it's already cq+, ignore the patch. - log("%s already has cq=%s" % (patch.id(), patch.commit_queue())) - return False - - # We only need to worry about patches from contributers who are not yet committers. - committer_record = CommitterList().committer_by_email(patch.attacher_email()) - if committer_record: - log("%s committer = %s" % (patch.id(), committer_record)) - return not committer_record - - def execute(self, options, args, tool): - patches = tool.bugs.queries.fetch_patches_from_pending_commit_list() - patches_needing_cq = filter(self._needs_commit_queue, patches) - if options.bugs: - bugs_needing_cq = map(lambda patch: patch.bug_id(), patches_needing_cq) - bugs_needing_cq = sorted(set(bugs_needing_cq)) - for bug_id in bugs_needing_cq: - print "%s" % tool.bugs.bug_url_for_bug_id(bug_id) - else: - for patch in patches_needing_cq: - print "%s" % tool.bugs.attachment_url_for_id(patch.id(), action="edit") - - -class PatchesToReview(AbstractDeclarativeCommand): - name = "patches-to-review" - help_text = "List patches that are pending review" - - def execute(self, options, args, tool): - patch_ids = tool.bugs.queries.fetch_attachment_ids_from_review_queue() - log("Patches pending review:") - for patch_id in patch_ids: - print patch_id - - -class LastGreenRevision(AbstractDeclarativeCommand): - name = "last-green-revision" - help_text = "Prints the last known good revision" - - def execute(self, options, args, tool): - print self._tool.buildbot.last_green_revision() - - -class WhatBroke(AbstractDeclarativeCommand): - name = "what-broke" - help_text = "Print failing buildbots (%s) and what revisions broke them" % BuildBot.default_host - - def _print_builder_line(self, builder_name, max_name_width, status_message): - print "%s : %s" % (builder_name.ljust(max_name_width), status_message) - - def _print_blame_information_for_builder(self, builder_status, name_width, avoid_flakey_tests=True): - builder = self._tool.buildbot.builder_with_name(builder_status["name"]) - red_build = builder.build(builder_status["build_number"]) - regression_window = builder.find_regression_window(red_build) - if not regression_window.failing_build(): - self._print_builder_line(builder.name(), name_width, "FAIL (error loading build information)") - return - if not regression_window.build_before_failure(): - self._print_builder_line(builder.name(), name_width, "FAIL (blame-list: sometime before %s?)" % regression_window.failing_build().revision()) - return - - revisions = regression_window.revisions() - first_failure_message = "" - if (regression_window.failing_build() == builder.build(builder_status["build_number"])): - first_failure_message = " FIRST FAILURE, possibly a flaky test" - self._print_builder_line(builder.name(), name_width, "FAIL (blame-list: %s%s)" % (revisions, first_failure_message)) - for revision in revisions: - commit_info = self._tool.checkout().commit_info_for_revision(revision) - if commit_info: - print commit_info.blame_string(self._tool.bugs) - else: - print "FAILED to fetch CommitInfo for r%s, likely missing ChangeLog" % revision - - def execute(self, options, args, tool): - builder_statuses = tool.buildbot.builder_statuses() - longest_builder_name = max(map(len, map(lambda builder: builder["name"], builder_statuses))) - failing_builders = 0 - for builder_status in builder_statuses: - # If the builder is green, print OK, exit. - if builder_status["is_green"]: - continue - self._print_blame_information_for_builder(builder_status, name_width=longest_builder_name) - failing_builders += 1 - if failing_builders: - print "%s of %s are failing" % (failing_builders, pluralize("builder", len(builder_statuses))) - else: - print "All builders are passing!" - - -class ResultsFor(AbstractDeclarativeCommand): - name = "results-for" - help_text = "Print a list of failures for the passed revision from bots on %s" % BuildBot.default_host - argument_names = "REVISION" - - def _print_layout_test_results(self, results): - if not results: - print " No results." - return - for title, files in results.parsed_results().items(): - print " %s" % title - for filename in files: - print " %s" % filename - - def execute(self, options, args, tool): - builders = self._tool.buildbot.builders() - for builder in builders: - print "%s:" % builder.name() - build = builder.build_for_revision(args[0], allow_failed_lookups=True) - self._print_layout_test_results(build.layout_test_results()) - - -class FailureReason(AbstractDeclarativeCommand): - name = "failure-reason" - help_text = "Lists revisions where individual test failures started at %s" % BuildBot.default_host - - def _blame_line_for_revision(self, revision): - try: - commit_info = self._tool.checkout().commit_info_for_revision(revision) - except Exception, e: - return "FAILED to fetch CommitInfo for r%s, exception: %s" % (revision, e) - if not commit_info: - return "FAILED to fetch CommitInfo for r%s, likely missing ChangeLog" % revision - return commit_info.blame_string(self._tool.bugs) - - def _print_blame_information_for_transition(self, regression_window, failing_tests): - red_build = regression_window.failing_build() - print "SUCCESS: Build %s (r%s) was the first to show failures: %s" % (red_build._number, red_build.revision(), failing_tests) - print "Suspect revisions:" - for revision in regression_window.revisions(): - print self._blame_line_for_revision(revision) - - def _explain_failures_for_builder(self, builder, start_revision): - print "Examining failures for \"%s\", starting at r%s" % (builder.name(), start_revision) - revision_to_test = start_revision - build = builder.build_for_revision(revision_to_test, allow_failed_lookups=True) - layout_test_results = build.layout_test_results() - if not layout_test_results: - # FIXME: This could be made more user friendly. - print "Failed to load layout test results; can't continue. (start revision = r%s)" % start_revision - return 1 - - results_to_explain = set(layout_test_results.failing_tests()) - last_build_with_results = build - print "Starting at %s" % revision_to_test - while results_to_explain: - revision_to_test -= 1 - new_build = builder.build_for_revision(revision_to_test, allow_failed_lookups=True) - if not new_build: - print "No build for %s" % revision_to_test - continue - build = new_build - latest_results = build.layout_test_results() - if not latest_results: - print "No results build %s (r%s)" % (build._number, build.revision()) - continue - failures = set(latest_results.failing_tests()) - if len(failures) >= 20: - # FIXME: We may need to move this logic into the LayoutTestResults class. - # The buildbot stops runs after 20 failures so we don't have full results to work with here. - print "Too many failures in build %s (r%s), ignoring." % (build._number, build.revision()) - continue - fixed_results = results_to_explain - failures - if not fixed_results: - print "No change in build %s (r%s), %s unexplained failures (%s in this build)" % (build._number, build.revision(), len(results_to_explain), len(failures)) - last_build_with_results = build - continue - regression_window = RegressionWindow(build, last_build_with_results) - self._print_blame_information_for_transition(regression_window, fixed_results) - last_build_with_results = build - results_to_explain -= fixed_results - if results_to_explain: - print "Failed to explain failures: %s" % results_to_explain - return 1 - print "Explained all results for %s" % builder.name() - return 0 - - def _builder_to_explain(self): - builder_statuses = self._tool.buildbot.builder_statuses() - red_statuses = [status for status in builder_statuses if not status["is_green"]] - print "%s failing" % (pluralize("builder", len(red_statuses))) - builder_choices = [status["name"] for status in red_statuses] - # We could offer an "All" choice here. - chosen_name = User.prompt_with_list("Which builder to diagnose:", builder_choices) - # FIXME: prompt_with_list should really take a set of objects and a set of names and then return the object. - for status in red_statuses: - if status["name"] == chosen_name: - return (self._tool.buildbot.builder_with_name(chosen_name), status["built_revision"]) - - def execute(self, options, args, tool): - (builder, latest_revision) = self._builder_to_explain() - start_revision = self._tool.user.prompt("Revision to walk backwards from? [%s] " % latest_revision) or latest_revision - if not start_revision: - print "Revision required." - return 1 - return self._explain_failures_for_builder(builder, start_revision=int(start_revision)) - - -class FindFlakyTests(AbstractDeclarativeCommand): - name = "find-flaky-tests" - help_text = "Lists tests that often fail for a single build at %s" % BuildBot.default_host - - def _find_failures(self, builder, revision): - build = builder.build_for_revision(revision, allow_failed_lookups=True) - if not build: - print "No build for %s" % revision - return (None, None) - results = build.layout_test_results() - if not results: - print "No results build %s (r%s)" % (build._number, build.revision()) - return (None, None) - failures = set(results.failing_tests()) - if len(failures) >= 20: - # FIXME: We may need to move this logic into the LayoutTestResults class. - # The buildbot stops runs after 20 failures so we don't have full results to work with here. - print "Too many failures in build %s (r%s), ignoring." % (build._number, build.revision()) - return (None, None) - return (build, failures) - - def _increment_statistics(self, flaky_tests, flaky_test_statistics): - for test in flaky_tests: - count = flaky_test_statistics.get(test, 0) - flaky_test_statistics[test] = count + 1 - - def _print_statistics(self, statistics): - print "=== Results ===" - print "Occurances Test name" - for value, key in sorted([(value, key) for key, value in statistics.items()]): - print "%10d %s" % (value, key) - - def _walk_backwards_from(self, builder, start_revision, limit): - flaky_test_statistics = {} - all_previous_failures = set([]) - one_time_previous_failures = set([]) - previous_build = None - for i in range(limit): - revision = start_revision - i - print "Analyzing %s ... " % revision, - (build, failures) = self._find_failures(builder, revision) - if failures == None: - # Notice that we don't loop on the empty set! - continue - print "has %s failures" % len(failures) - flaky_tests = one_time_previous_failures - failures - if flaky_tests: - print "Flaky tests: %s %s" % (sorted(flaky_tests), - previous_build.results_url()) - self._increment_statistics(flaky_tests, flaky_test_statistics) - one_time_previous_failures = failures - all_previous_failures - all_previous_failures = failures - previous_build = build - self._print_statistics(flaky_test_statistics) - - def _builder_to_analyze(self): - statuses = self._tool.buildbot.builder_statuses() - choices = [status["name"] for status in statuses] - chosen_name = User.prompt_with_list("Which builder to analyze:", choices) - for status in statuses: - if status["name"] == chosen_name: - return (self._tool.buildbot.builder_with_name(chosen_name), status["built_revision"]) - - def execute(self, options, args, tool): - (builder, latest_revision) = self._builder_to_analyze() - limit = self._tool.user.prompt("How many revisions to look through? [10000] ") or 10000 - return self._walk_backwards_from(builder, latest_revision, limit=int(limit)) - - -class TreeStatus(AbstractDeclarativeCommand): - name = "tree-status" - help_text = "Print the status of the %s buildbots" % BuildBot.default_host - long_help = """Fetches build status from http://build.webkit.org/one_box_per_builder -and displayes the status of each builder.""" - - def execute(self, options, args, tool): - for builder in tool.buildbot.builder_statuses(): - status_string = "ok" if builder["is_green"] else "FAIL" - print "%s : %s" % (status_string.ljust(4), builder["name"]) - - -class SkippedPorts(AbstractDeclarativeCommand): - name = "skipped-ports" - help_text = "Print the list of ports skipping the given layout test(s)" - long_help = """Scans the the Skipped file of each port and figure -out what ports are skipping the test(s). Categories are taken in account too.""" - argument_names = "TEST_NAME" - - def execute(self, options, args, tool): - class Options: - # Required for chromium port. - use_drt = True - - results = dict([(test_name, []) for test_name in args]) - for port_name, port_object in tool.port_factory.get_all(options=Options).iteritems(): - for test_name in args: - if port_object.skips_layout_test(test_name): - results[test_name].append(port_name) - - for test_name, ports in results.iteritems(): - if ports: - print "Ports skipping test %r: %s" % (test_name, ', '.join(ports)) - else: - print "Test %r is not skipped by any port." % test_name diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queries_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/queries_unittest.py deleted file mode 100644 index 05a4a5c..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/queries_unittest.py +++ /dev/null @@ -1,90 +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.bugzilla import Bugzilla -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.commands.commandtest import CommandsTest -from webkitpy.tool.commands.queries import * -from webkitpy.tool.mocktool import MockTool - - -class QueryCommandsTest(CommandsTest): - def test_bugs_to_commit(self): - expected_stderr = "Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)\n" - self.assert_execute_outputs(BugsToCommit(), None, "42\n77\n", expected_stderr) - - def test_patches_in_commit_queue(self): - expected_stdout = "http://example.com/197\nhttp://example.com/103\n" - expected_stderr = "Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)\nPatches in commit queue:\n" - self.assert_execute_outputs(PatchesInCommitQueue(), None, expected_stdout, expected_stderr) - - def test_patches_to_commit_queue(self): - expected_stdout = "http://example.com/104&action=edit\n" - expected_stderr = "197 already has cq=+\n128 already has cq=+\n105 committer = \"Eric Seidel\" <eric@webkit.org>\n" - options = Mock() - options.bugs = False - self.assert_execute_outputs(PatchesToCommitQueue(), None, expected_stdout, expected_stderr, options=options) - - expected_stdout = "http://example.com/77\n" - options.bugs = True - self.assert_execute_outputs(PatchesToCommitQueue(), None, expected_stdout, expected_stderr, options=options) - - def test_patches_to_review(self): - expected_stdout = "103\n" - expected_stderr = "Patches pending review:\n" - self.assert_execute_outputs(PatchesToReview(), None, expected_stdout, expected_stderr) - - def test_tree_status(self): - expected_stdout = "ok : Builder1\nok : Builder2\n" - self.assert_execute_outputs(TreeStatus(), None, expected_stdout) - - def test_skipped_ports(self): - expected_stdout = "Ports skipping test 'media/foo/bar.html': test_port1, test_port2\n" - self.assert_execute_outputs(SkippedPorts(), ("media/foo/bar.html",), expected_stdout) - - expected_stdout = "Ports skipping test 'foo': test_port1\n" - self.assert_execute_outputs(SkippedPorts(), ("foo",), expected_stdout) - - expected_stdout = "Test 'media' is not skipped by any port.\n" - self.assert_execute_outputs(SkippedPorts(), ("media",), expected_stdout) - - -class FailureReasonTest(unittest.TestCase): - def test_blame_line_for_revision(self): - tool = MockTool() - command = FailureReason() - command.bind_to_tool(tool) - # This is an artificial example, mostly to test the CommitInfo lookup failure case. - self.assertEquals(command._blame_line_for_revision(None), "FAILED to fetch CommitInfo for rNone, likely missing ChangeLog") - - def raising_mock(self): - raise Exception("MESSAGE") - tool.checkout().commit_info_for_revision = raising_mock - self.assertEquals(command._blame_line_for_revision(None), "FAILED to fetch CommitInfo for rNone, exception: MESSAGE") diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queues.py b/WebKitTools/Scripts/webkitpy/tool/commands/queues.py deleted file mode 100644 index bfaeb08..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/queues.py +++ /dev/null @@ -1,417 +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 codecs -import time -import traceback -import os - -from datetime import datetime -from optparse import make_option -from StringIO import StringIO - -from webkitpy.common.config.committervalidator import CommitterValidator -from webkitpy.common.net.bugzilla import Attachment -from webkitpy.common.net.layouttestresults import path_for_layout_test, LayoutTestResults -from webkitpy.common.net.statusserver import StatusServer -from webkitpy.common.system.deprecated_logging import error, log -from webkitpy.common.system.executive import ScriptError -from webkitpy.tool.bot.commitqueuetask import CommitQueueTask, CommitQueueTaskDelegate -from webkitpy.tool.bot.feeders import CommitQueueFeeder, EWSFeeder -from webkitpy.tool.bot.queueengine import QueueEngine, QueueEngineDelegate -from webkitpy.tool.commands.stepsequence import StepSequenceErrorHandler -from webkitpy.tool.grammar import pluralize, join_with_separators -from webkitpy.tool.multicommandtool import Command, TryAgain - - -class AbstractQueue(Command, QueueEngineDelegate): - watchers = [ - ] - - _pass_status = "Pass" - _fail_status = "Fail" - _retry_status = "Retry" - _error_status = "Error" - - def __init__(self, options=None): # Default values should never be collections (like []) as default values are shared between invocations - options_list = (options or []) + [ - make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Do not ask the user for confirmation before running the queue. Dangerous!"), - make_option("--exit-after-iteration", action="store", type="int", dest="iterations", default=None, help="Stop running the queue after iterating this number of times."), - ] - Command.__init__(self, "Run the %s" % self.name, options=options_list) - self._iteration_count = 0 - - def _cc_watchers(self, bug_id): - try: - self._tool.bugs.add_cc_to_bug(bug_id, self.watchers) - except Exception, e: - traceback.print_exc() - log("Failed to CC watchers.") - - def run_webkit_patch(self, args): - webkit_patch_args = [self._tool.path()] - # FIXME: This is a hack, we should have a more general way to pass global options. - # FIXME: We must always pass global options and their value in one argument - # because our global option code looks for the first argument which does - # not begin with "-" and assumes that is the command name. - webkit_patch_args += ["--status-host=%s" % self._tool.status_server.host] - if self._tool.status_server.bot_id: - webkit_patch_args += ["--bot-id=%s" % self._tool.status_server.bot_id] - if self._options.port: - webkit_patch_args += ["--port=%s" % self._options.port] - webkit_patch_args.extend(args) - # FIXME: There is probably no reason to use run_and_throw_if_fail anymore. - # run_and_throw_if_fail was invented to support tee'd output - # (where we write both to a log file and to the console at once), - # but the queues don't need live-progress, a dump-of-output at the - # end should be sufficient. - return self._tool.executive.run_and_throw_if_fail(webkit_patch_args) - - def _log_directory(self): - return "%s-logs" % self.name - - # QueueEngineDelegate methods - - def queue_log_path(self): - return os.path.join(self._log_directory(), "%s.log" % self.name) - - def work_item_log_path(self, work_item): - raise NotImplementedError, "subclasses must implement" - - def begin_work_queue(self): - log("CAUTION: %s will discard all local changes in \"%s\"" % (self.name, self._tool.scm().checkout_root)) - if self._options.confirm: - response = self._tool.user.prompt("Are you sure? Type \"yes\" to continue: ") - if (response != "yes"): - error("User declined.") - log("Running WebKit %s." % self.name) - self._tool.status_server.update_status(self.name, "Starting Queue") - - def stop_work_queue(self, reason): - self._tool.status_server.update_status(self.name, "Stopping Queue, reason: %s" % reason) - - def should_continue_work_queue(self): - self._iteration_count += 1 - return not self._options.iterations or self._iteration_count <= self._options.iterations - - def next_work_item(self): - raise NotImplementedError, "subclasses must implement" - - def should_proceed_with_work_item(self, work_item): - raise NotImplementedError, "subclasses must implement" - - def process_work_item(self, work_item): - raise NotImplementedError, "subclasses must implement" - - def handle_unexpected_error(self, work_item, message): - raise NotImplementedError, "subclasses must implement" - - # Command methods - - def execute(self, options, args, tool, engine=QueueEngine): - self._options = options # FIXME: This code is wrong. Command.options is a list, this assumes an Options element! - self._tool = tool # FIXME: This code is wrong too! Command.bind_to_tool handles this! - return engine(self.name, self, self._tool.wakeup_event).run() - - @classmethod - def _log_from_script_error_for_upload(cls, script_error, output_limit=None): - # We have seen request timeouts with app engine due to large - # log uploads. Trying only the last 512k. - if not output_limit: - output_limit = 512 * 1024 # 512k - output = script_error.message_with_output(output_limit=output_limit) - # We pre-encode the string to a byte array before passing it - # to status_server, because ClientForm (part of mechanize) - # wants a file-like object with pre-encoded data. - return StringIO(output.encode("utf-8")) - - @classmethod - def _update_status_for_script_error(cls, tool, state, script_error, is_error=False): - message = str(script_error) - if is_error: - message = "Error: %s" % message - failure_log = cls._log_from_script_error_for_upload(script_error) - return tool.status_server.update_status(cls.name, message, state["patch"], failure_log) - - -class FeederQueue(AbstractQueue): - name = "feeder-queue" - - _sleep_duration = 30 # seconds - - # AbstractPatchQueue methods - - def begin_work_queue(self): - AbstractQueue.begin_work_queue(self) - self.feeders = [ - CommitQueueFeeder(self._tool), - EWSFeeder(self._tool), - ] - - def next_work_item(self): - # This really show inherit from some more basic class that doesn't - # understand work items, but the base class in the heirarchy currently - # understands work items. - return "synthetic-work-item" - - def should_proceed_with_work_item(self, work_item): - return True - - def process_work_item(self, work_item): - for feeder in self.feeders: - feeder.feed() - time.sleep(self._sleep_duration) - return True - - def work_item_log_path(self, work_item): - return None - - def handle_unexpected_error(self, work_item, message): - log(message) - - -class AbstractPatchQueue(AbstractQueue): - def _update_status(self, message, patch=None, results_file=None): - return self._tool.status_server.update_status(self.name, message, patch, results_file) - - def _next_patch(self): - patch_id = self._tool.status_server.next_work_item(self.name) - if not patch_id: - return None - patch = self._tool.bugs.fetch_attachment(patch_id) - if not patch: - # FIXME: Using a fake patch because release_work_item has the wrong API. - # We also don't really need to release the lock (although that's fine), - # mostly we just need to remove this bogus patch from our queue. - # If for some reason bugzilla is just down, then it will be re-fed later. - patch = Attachment({'id': patch_id}, None) - self._release_work_item(patch) - return None - return patch - - def _release_work_item(self, patch): - self._tool.status_server.release_work_item(self.name, patch) - - def _did_pass(self, patch): - self._update_status(self._pass_status, patch) - self._release_work_item(patch) - - def _did_fail(self, patch): - self._update_status(self._fail_status, patch) - self._release_work_item(patch) - - def _did_retry(self, patch): - self._update_status(self._retry_status, patch) - self._release_work_item(patch) - - def _did_error(self, patch, reason): - message = "%s: %s" % (self._error_status, reason) - self._update_status(message, patch) - self._release_work_item(patch) - - def work_item_log_path(self, patch): - return os.path.join(self._log_directory(), "%s.log" % patch.bug_id()) - - -class CommitQueue(AbstractPatchQueue, StepSequenceErrorHandler, CommitQueueTaskDelegate): - name = "commit-queue" - - # AbstractPatchQueue methods - - def begin_work_queue(self): - AbstractPatchQueue.begin_work_queue(self) - self.committer_validator = CommitterValidator(self._tool.bugs) - - def next_work_item(self): - return self._next_patch() - - def should_proceed_with_work_item(self, patch): - patch_text = "rollout patch" if patch.is_rollout() else "patch" - self._update_status("Processing %s" % patch_text, patch) - return True - - def process_work_item(self, patch): - self._cc_watchers(patch.bug_id()) - task = CommitQueueTask(self, patch) - try: - if task.run(): - self._did_pass(patch) - return True - self._did_retry(patch) - except ScriptError, e: - validator = CommitterValidator(self._tool.bugs) - validator.reject_patch_from_commit_queue(patch.id(), self._error_message_for_bug(task.failure_status_id, e)) - self._did_fail(patch) - - def _error_message_for_bug(self, status_id, script_error): - if not script_error.output: - return script_error.message_with_output() - results_link = self._tool.status_server.results_url_for_status(status_id) - return "%s\nFull output: %s" % (script_error.message_with_output(), results_link) - - def handle_unexpected_error(self, patch, message): - self.committer_validator.reject_patch_from_commit_queue(patch.id(), message) - - # CommitQueueTaskDelegate methods - - def run_command(self, command): - self.run_webkit_patch(command) - - def command_passed(self, message, patch): - self._update_status(message, patch=patch) - - def command_failed(self, message, script_error, patch): - failure_log = self._log_from_script_error_for_upload(script_error) - return self._update_status(message, patch=patch, results_file=failure_log) - - # FIXME: This exists for mocking, but should instead be mocked via - # some sort of tool.filesystem() object. - def _read_file_contents(self, path): - try: - with codecs.open(path, "r", "utf-8") as open_file: - return open_file.read() - except OSError, e: # File does not exist or can't be read. - return None - - # FIXME: This may belong on the Port object. - def layout_test_results(self): - results_path = self._tool.port().layout_tests_results_path() - results_html = self._read_file_contents(results_path) - if not results_html: - return None - return LayoutTestResults.results_from_string(results_html) - - def refetch_patch(self, patch): - return self._tool.bugs.fetch_attachment(patch.id()) - - def _author_emails_for_tests(self, flaky_tests): - test_paths = map(path_for_layout_test, flaky_tests) - commit_infos = self._tool.checkout().recent_commit_infos_for_files(test_paths) - return set([commit_info.author().bugzilla_email() for commit_info in commit_infos if commit_info.author()]) - - def report_flaky_tests(self, patch, flaky_tests): - message = "The %s encountered the following flaky tests while processing attachment %s:" % (self.name, patch.id()) - message += "\n\n%s\n\n" % ("\n".join(flaky_tests)) - message += "Please file bugs against the tests. " - author_emails = self._author_emails_for_tests(flaky_tests) - if author_emails: - message += "These tests were authored by %s. " % (join_with_separators(sorted(author_emails))) - message += "The commit-queue is continuing to process your patch." - self._tool.bugs.post_comment_to_bug(patch.bug_id(), message) - - # StepSequenceErrorHandler methods - - def handle_script_error(cls, tool, state, script_error): - # Hitting this error handler should be pretty rare. It does occur, - # however, when a patch no longer applies to top-of-tree in the final - # land step. - log(script_error.message_with_output()) - - @classmethod - def handle_checkout_needs_update(cls, tool, state, options, error): - message = "Tests passed, but commit failed (checkout out of date). Updating, then landing without building or re-running tests." - tool.status_server.update_status(cls.name, message, state["patch"]) - # The only time when we find out that out checkout needs update is - # when we were ready to actually pull the trigger and land the patch. - # Rather than spinning in the master process, we retry without - # building or testing, which is much faster. - options.build = False - options.test = False - options.update = True - raise TryAgain() - - -class AbstractReviewQueue(AbstractPatchQueue, StepSequenceErrorHandler): - """This is the base-class for the EWS queues and the style-queue.""" - def __init__(self, options=None): - AbstractPatchQueue.__init__(self, options) - - def review_patch(self, patch): - raise NotImplementedError("subclasses must implement") - - # AbstractPatchQueue methods - - def begin_work_queue(self): - AbstractPatchQueue.begin_work_queue(self) - - def next_work_item(self): - return self._next_patch() - - def should_proceed_with_work_item(self, patch): - raise NotImplementedError("subclasses must implement") - - def process_work_item(self, patch): - try: - if not self.review_patch(patch): - return False - self._did_pass(patch) - return True - except ScriptError, e: - if e.exit_code != QueueEngine.handled_error_code: - self._did_fail(patch) - else: - # The subprocess handled the error, but won't have released the patch, so we do. - # FIXME: We need to simplify the rules by which _release_work_item is called. - self._release_work_item(patch) - raise e - - def handle_unexpected_error(self, patch, message): - log(message) - - # StepSequenceErrorHandler methods - - @classmethod - def handle_script_error(cls, tool, state, script_error): - log(script_error.message_with_output()) - - -class StyleQueue(AbstractReviewQueue): - name = "style-queue" - def __init__(self): - AbstractReviewQueue.__init__(self) - - def should_proceed_with_work_item(self, patch): - self._update_status("Checking style", patch) - return True - - def review_patch(self, patch): - self.run_webkit_patch(["check-style", "--force-clean", "--non-interactive", "--parent-command=style-queue", patch.id()]) - return True - - @classmethod - def handle_script_error(cls, tool, state, script_error): - is_svn_apply = script_error.command_name() == "svn-apply" - status_id = cls._update_status_for_script_error(tool, state, script_error, is_error=is_svn_apply) - if is_svn_apply: - QueueEngine.exit_after_handled_error(script_error) - message = "Attachment %s did not pass %s:\n\n%s\n\nIf any of these errors are false positives, please file a bug against check-webkit-style." % (state["patch"].id(), cls.name, script_error.message_with_output(output_limit=3*1024)) - tool.bugs.post_comment_to_bug(state["patch"].bug_id(), message, cc=cls.watchers) - exit(1) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py deleted file mode 100644 index b45db7b..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/queues_unittest.py +++ /dev/null @@ -1,378 +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 - -from webkitpy.common.checkout.scm import CheckoutNeedsUpdate -from webkitpy.common.config.committers import Committer -from webkitpy.common.net.bugzilla import Attachment -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.commands.commandtest import CommandsTest -from webkitpy.tool.commands.queues import * -from webkitpy.tool.commands.queuestest import QueuesTest -from webkitpy.tool.commands.stepsequence import StepSequence -from webkitpy.tool.mocktool import MockTool, MockSCM, MockStatusServer - - -class TestQueue(AbstractPatchQueue): - name = "test-queue" - - -class TestReviewQueue(AbstractReviewQueue): - name = "test-review-queue" - - -class TestFeederQueue(FeederQueue): - _sleep_duration = 0 - - -class AbstractQueueTest(CommandsTest): - def test_log_directory(self): - self.assertEquals(TestQueue()._log_directory(), "test-queue-logs") - - def _assert_run_webkit_patch(self, run_args, port=None): - queue = TestQueue() - tool = MockTool() - tool.status_server.bot_id = "gort" - tool.executive = Mock() - queue.bind_to_tool(tool) - queue._options = Mock() - queue._options.port = port - - queue.run_webkit_patch(run_args) - expected_run_args = ["echo", "--status-host=example.com", "--bot-id=gort"] - if port: - expected_run_args.append("--port=%s" % port) - expected_run_args.extend(run_args) - tool.executive.run_and_throw_if_fail.assert_called_with(expected_run_args) - - def test_run_webkit_patch(self): - self._assert_run_webkit_patch([1]) - self._assert_run_webkit_patch(["one", 2]) - self._assert_run_webkit_patch([1], port="mockport") - - def test_iteration_count(self): - queue = TestQueue() - queue._options = Mock() - queue._options.iterations = 3 - self.assertTrue(queue.should_continue_work_queue()) - self.assertTrue(queue.should_continue_work_queue()) - self.assertTrue(queue.should_continue_work_queue()) - self.assertFalse(queue.should_continue_work_queue()) - - def test_no_iteration_count(self): - queue = TestQueue() - queue._options = Mock() - self.assertTrue(queue.should_continue_work_queue()) - self.assertTrue(queue.should_continue_work_queue()) - self.assertTrue(queue.should_continue_work_queue()) - self.assertTrue(queue.should_continue_work_queue()) - - def _assert_log_message(self, script_error, log_message): - failure_log = AbstractQueue._log_from_script_error_for_upload(script_error, output_limit=10) - self.assertTrue(failure_log.read(), log_message) - - def test_log_from_script_error_for_upload(self): - self._assert_log_message(ScriptError("test"), "test") - # In python 2.5 unicode(Exception) is busted. See: - # http://bugs.python.org/issue2517 - # With no good workaround, we just ignore these tests. - if not hasattr(Exception, "__unicode__"): - return - - unicode_tor = u"WebKit \u2661 Tor Arne Vestb\u00F8!" - utf8_tor = unicode_tor.encode("utf-8") - self._assert_log_message(ScriptError(unicode_tor), utf8_tor) - script_error = ScriptError(unicode_tor, output=unicode_tor) - expected_output = "%s\nLast %s characters of output:\n%s" % (utf8_tor, 10, utf8_tor[-10:]) - self._assert_log_message(script_error, expected_output) - - -class FeederQueueTest(QueuesTest): - def test_feeder_queue(self): - queue = TestFeederQueue() - tool = MockTool(log_executive=True) - expected_stderr = { - "begin_work_queue": self._default_begin_work_queue_stderr("feeder-queue", MockSCM.fake_checkout_root), - "should_proceed_with_work_item": "", - "next_work_item": "", - "process_work_item": """Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) -Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com) -MOCK setting flag 'commit-queue' to '-' on attachment '128' with comment 'Rejecting patch 128 from commit-queue.' and additional comment 'non-committer@example.com does not have committer permissions according to http://trac.webkit.org/browser/trunk/WebKitTools/Scripts/webkitpy/common/config/committers.py. - -- If you do not have committer rights please read http://webkit.org/coding/contributing.html for instructions on how to use bugzilla flags. - -- If you have committer 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 committer rights.' -MOCK: update_work_items: commit-queue [106, 197] -Feeding commit-queue items [106, 197] -Feeding EWS (1 r? patch, 1 new) -MOCK: submit_to_ews: 103 -""", - "handle_unexpected_error": "Mock error message\n", - } - self.assert_queue_outputs(queue, tool=tool, expected_stderr=expected_stderr) - - -class AbstractPatchQueueTest(CommandsTest): - def test_next_patch(self): - queue = AbstractPatchQueue() - tool = MockTool() - queue.bind_to_tool(tool) - queue._options = Mock() - queue._options.port = None - self.assertEquals(queue._next_patch(), None) - tool.status_server = MockStatusServer(work_items=[2, 197]) - expected_stdout = "MOCK: fetch_attachment: 2 is not a known attachment id\n" # A mock-only message to prevent us from making mistakes. - expected_stderr = "MOCK: release_work_item: None 2\n" - patch_id = OutputCapture().assert_outputs(self, queue._next_patch, [], expected_stdout=expected_stdout, expected_stderr=expected_stderr) - self.assertEquals(patch_id, None) # 2 is an invalid patch id - self.assertEquals(queue._next_patch().id(), 197) - - -class NeedsUpdateSequence(StepSequence): - def _run(self, tool, options, state): - raise CheckoutNeedsUpdate([], 1, "", None) - - -class AlwaysCommitQueueTool(object): - def __init__(self): - self.status_server = MockStatusServer() - - def command_by_name(self, name): - return CommitQueue - - -class SecondThoughtsCommitQueue(CommitQueue): - def __init__(self): - self._reject_patch = False - CommitQueue.__init__(self) - - def run_command(self, command): - # We want to reject the patch after the first validation, - # so wait to reject it until after some other command has run. - self._reject_patch = True - return CommitQueue.run_command(self, command) - - def refetch_patch(self, patch): - if not self._reject_patch: - return self._tool.bugs.fetch_attachment(patch.id()) - - attachment_dictionary = { - "id": patch.id(), - "bug_id": patch.bug_id(), - "name": "Rejected", - "is_obsolete": True, - "is_patch": False, - "review": "-", - "reviewer_email": "foo@bar.com", - "commit-queue": "-", - "committer_email": "foo@bar.com", - "attacher_email": "Contributer1", - } - return Attachment(attachment_dictionary, None) - - -# Creating fake CommitInfos is a pain, so we use a mock one here. -class MockCommitInfo(object): - def __init__(self, author_email): - self._author_email = author_email - - def author(self): - # It's definitely possible to have commits with authors who - # are not in our committers.py list. - if not self._author_email: - return None - return Committer("Mock Committer", self._author_email) - - -class CommitQueueTest(QueuesTest): - def test_commit_queue(self): - expected_stderr = { - "begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root), - "should_proceed_with_work_item": "MOCK: update_status: commit-queue Processing patch\n", - "next_work_item": "", - "process_work_item": """MOCK: update_status: commit-queue Applied patch -MOCK: update_status: commit-queue Built patch -MOCK: update_status: commit-queue Passed tests -MOCK: update_status: commit-queue Landed patch -MOCK: update_status: commit-queue Pass -MOCK: release_work_item: commit-queue 197 -""", - "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'Mock error message'\n", - "handle_script_error": "ScriptError error message\n", - } - self.assert_queue_outputs(CommitQueue(), expected_stderr=expected_stderr) - - def test_commit_queue_failure(self): - expected_stderr = { - "begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root), - "should_proceed_with_work_item": "MOCK: update_status: commit-queue Processing patch\n", - "next_work_item": "", - "process_work_item": """MOCK: update_status: commit-queue Patch does not apply -MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'MOCK script error' -MOCK: update_status: commit-queue Fail -MOCK: release_work_item: commit-queue 197 -""", - "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'Mock error message'\n", - "handle_script_error": "ScriptError error message\n", - } - queue = CommitQueue() - - def mock_run_webkit_patch(command): - raise ScriptError('MOCK script error') - - queue.run_webkit_patch = mock_run_webkit_patch - self.assert_queue_outputs(queue, expected_stderr=expected_stderr) - - def test_rollout(self): - tool = MockTool(log_executive=True) - tool.buildbot.light_tree_on_fire() - expected_stderr = { - "begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root), - "should_proceed_with_work_item": "MOCK: update_status: commit-queue Processing patch\n", - "next_work_item": "", - "process_work_item": """MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'apply-attachment', '--force-clean', '--non-interactive', 197] -MOCK: update_status: commit-queue Applied patch -MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build', '--no-clean', '--no-update', '--build-style=both'] -MOCK: update_status: commit-queue Built patch -MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build-and-test', '--no-clean', '--no-update', '--test', '--non-interactive'] -MOCK: update_status: commit-queue Passed tests -MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--ignore-builders', '--non-interactive', '--parent-command=commit-queue', 197] -MOCK: update_status: commit-queue Landed patch -MOCK: update_status: commit-queue Pass -MOCK: release_work_item: commit-queue 197 -""", - "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '197' with comment 'Rejecting patch 197 from commit-queue.' and additional comment 'Mock error message'\n", - "handle_script_error": "ScriptError error message\n", - } - self.assert_queue_outputs(CommitQueue(), tool=tool, expected_stderr=expected_stderr) - - def test_rollout_lands(self): - tool = MockTool(log_executive=True) - tool.buildbot.light_tree_on_fire() - rollout_patch = tool.bugs.fetch_attachment(106) # _patch6, a rollout patch. - assert(rollout_patch.is_rollout()) - expected_stderr = { - "begin_work_queue": self._default_begin_work_queue_stderr("commit-queue", MockSCM.fake_checkout_root), - "should_proceed_with_work_item": "MOCK: update_status: commit-queue Processing rollout patch\n", - "next_work_item": "", - "process_work_item": """MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'apply-attachment', '--force-clean', '--non-interactive', 106] -MOCK: update_status: commit-queue Applied patch -MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'build', '--no-clean', '--no-update', '--build-style=both'] -MOCK: update_status: commit-queue Built patch -MOCK run_and_throw_if_fail: ['echo', '--status-host=example.com', 'land-attachment', '--force-clean', '--ignore-builders', '--non-interactive', '--parent-command=commit-queue', 106] -MOCK: update_status: commit-queue Landed patch -MOCK: update_status: commit-queue Pass -MOCK: release_work_item: commit-queue 106 -""", - "handle_unexpected_error": "MOCK setting flag 'commit-queue' to '-' on attachment '106' with comment 'Rejecting patch 106 from commit-queue.' and additional comment 'Mock error message'\n", - "handle_script_error": "ScriptError error message\n", - } - self.assert_queue_outputs(CommitQueue(), tool=tool, work_item=rollout_patch, expected_stderr=expected_stderr) - - def test_auto_retry(self): - queue = CommitQueue() - options = Mock() - options.parent_command = "commit-queue" - tool = AlwaysCommitQueueTool() - sequence = NeedsUpdateSequence(None) - - expected_stderr = "Commit failed because the checkout is out of date. Please update and try again.\nMOCK: update_status: commit-queue Tests passed, but commit failed (checkout out of date). Updating, then landing without building or re-running tests.\n" - state = {'patch': None} - OutputCapture().assert_outputs(self, sequence.run_and_handle_errors, [tool, options, state], expected_exception=TryAgain, expected_stderr=expected_stderr) - - self.assertEquals(options.update, True) - self.assertEquals(options.build, False) - self.assertEquals(options.test, False) - - def test_manual_reject_during_processing(self): - queue = SecondThoughtsCommitQueue() - queue.bind_to_tool(MockTool()) - queue._options = Mock() - queue._options.port = None - expected_stderr = """MOCK: update_status: commit-queue Applied patch -MOCK: update_status: commit-queue Built patch -MOCK: update_status: commit-queue Passed tests -MOCK: update_status: commit-queue Retry -MOCK: release_work_item: commit-queue 197 -""" - OutputCapture().assert_outputs(self, queue.process_work_item, [QueuesTest.mock_work_item], expected_stderr=expected_stderr) - - def _assert_emails_for_tests(self, emails): - queue = CommitQueue() - tool = MockTool() - queue.bind_to_tool(tool) - commit_infos = [MockCommitInfo(email) for email in emails] - tool.checkout().recent_commit_infos_for_files = lambda paths: set(commit_infos) - self.assertEqual(queue._author_emails_for_tests([]), set(emails)) - - def test_author_emails_for_tests(self): - self._assert_emails_for_tests([]) - self._assert_emails_for_tests(["test1@test.com", "test1@test.com"]) - self._assert_emails_for_tests(["test1@test.com", "test2@test.com"]) - - def test_report_flaky_tests(self): - queue = CommitQueue() - queue.bind_to_tool(MockTool()) - expected_stderr = """MOCK bug comment: bug_id=42, cc=None ---- Begin comment --- -The commit-queue encountered the following flaky tests while processing attachment 197: - -foo/bar.html -bar/baz.html - -Please file bugs against the tests. These tests were authored by abarth@webkit.org. The commit-queue is continuing to process your patch. ---- End comment --- - -""" - OutputCapture().assert_outputs(self, queue.report_flaky_tests, [QueuesTest.mock_work_item, ["foo/bar.html", "bar/baz.html"]], expected_stderr=expected_stderr) - - def test_layout_test_results(self): - queue = CommitQueue() - queue.bind_to_tool(MockTool()) - queue._read_file_contents = lambda path: None - self.assertEquals(queue.layout_test_results(), None) - queue._read_file_contents = lambda path: "" - self.assertEquals(queue.layout_test_results(), None) - - -class StyleQueueTest(QueuesTest): - def test_style_queue(self): - expected_stderr = { - "begin_work_queue": self._default_begin_work_queue_stderr("style-queue", MockSCM.fake_checkout_root), - "next_work_item": "", - "should_proceed_with_work_item": "MOCK: update_status: style-queue Checking style\n", - "process_work_item": "MOCK: update_status: style-queue Pass\nMOCK: release_work_item: style-queue 197\n", - "handle_unexpected_error": "Mock error message\n", - "handle_script_error": "MOCK: update_status: style-queue ScriptError error message\nMOCK bug comment: bug_id=42, cc=[]\n--- Begin comment ---\nAttachment 197 did not pass style-queue:\n\nScriptError error message\n\nIf any of these errors are false positives, please file a bug against check-webkit-style.\n--- End comment ---\n\n", - } - expected_exceptions = { - "handle_script_error": SystemExit, - } - self.assert_queue_outputs(StyleQueue(), expected_stderr=expected_stderr, expected_exceptions=expected_exceptions) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py b/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py deleted file mode 100644 index 6455617..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/queuestest.py +++ /dev/null @@ -1,95 +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.bugzilla import Attachment -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.common.system.executive import ScriptError -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.commands.stepsequence import StepSequenceErrorHandler -from webkitpy.tool.mocktool import MockTool - - -class MockQueueEngine(object): - def __init__(self, name, queue, wakeup_event): - pass - - def run(self): - pass - - -class QueuesTest(unittest.TestCase): - # This is _patch1 in mocktool.py - mock_work_item = MockTool().bugs.fetch_attachment(197) - - def assert_outputs(self, func, func_name, args, expected_stdout, expected_stderr, expected_exceptions): - exception = None - if expected_exceptions and func_name in expected_exceptions: - exception = expected_exceptions[func_name] - - OutputCapture().assert_outputs(self, - func, - args=args, - expected_stdout=expected_stdout.get(func_name, ""), - expected_stderr=expected_stderr.get(func_name, ""), - expected_exception=exception) - - def _default_begin_work_queue_stderr(self, name, checkout_dir): - string_replacements = {"name": name, 'checkout_dir': checkout_dir} - return "CAUTION: %(name)s will discard all local changes in \"%(checkout_dir)s\"\nRunning WebKit %(name)s.\nMOCK: update_status: %(name)s Starting Queue\n" % string_replacements - - def assert_queue_outputs(self, queue, args=None, work_item=None, expected_stdout=None, expected_stderr=None, expected_exceptions=None, options=None, tool=None): - if not tool: - tool = MockTool() - if not expected_stdout: - expected_stdout = {} - if not expected_stderr: - expected_stderr = {} - if not args: - args = [] - if not options: - options = Mock() - options.port = None - if not work_item: - work_item = self.mock_work_item - tool.user.prompt = lambda message: "yes" - - queue.execute(options, args, tool, engine=MockQueueEngine) - - self.assert_outputs(queue.queue_log_path, "queue_log_path", [], expected_stdout, expected_stderr, expected_exceptions) - self.assert_outputs(queue.work_item_log_path, "work_item_log_path", [work_item], expected_stdout, expected_stderr, expected_exceptions) - self.assert_outputs(queue.begin_work_queue, "begin_work_queue", [], expected_stdout, expected_stderr, expected_exceptions) - self.assert_outputs(queue.should_continue_work_queue, "should_continue_work_queue", [], expected_stdout, expected_stderr, expected_exceptions) - self.assert_outputs(queue.next_work_item, "next_work_item", [], expected_stdout, expected_stderr, expected_exceptions) - self.assert_outputs(queue.should_proceed_with_work_item, "should_proceed_with_work_item", [work_item], expected_stdout, expected_stderr, expected_exceptions) - self.assert_outputs(queue.process_work_item, "process_work_item", [work_item], expected_stdout, expected_stderr, expected_exceptions) - self.assert_outputs(queue.handle_unexpected_error, "handle_unexpected_error", [work_item, "Mock error message"], expected_stdout, expected_stderr, expected_exceptions) - # Should we have a different function for testing StepSequenceErrorHandlers? - if isinstance(queue, StepSequenceErrorHandler): - self.assert_outputs(queue.handle_script_error, "handle_script_error", [tool, {"patch": self.mock_work_item}, ScriptError(message="ScriptError error message", script_args="MockErrorCommand")], expected_stdout, expected_stderr, expected_exceptions) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/rebaseline.py b/WebKitTools/Scripts/webkitpy/tool/commands/rebaseline.py deleted file mode 100644 index abfa850..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/rebaseline.py +++ /dev/null @@ -1,114 +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 re -import shutil -import urllib - -from webkitpy.common.net.buildbot import BuildBot, LayoutTestResults -from webkitpy.common.system.user import User -from webkitpy.layout_tests.port import factory -from webkitpy.tool.grammar import pluralize -from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand - - -# FIXME: I'm not sure where this logic should go in the end. -# For now it's here, until we have a second need for it. -class BuilderToPort(object): - _builder_name_to_port_name = { - r"SnowLeopard": "mac-snowleopard", - r"Leopard": "mac-leopard", - r"Tiger": "mac-tiger", - r"Windows": "win", - r"GTK": "gtk", - r"Qt": "qt", - r"Chromium Mac": "chromium-mac", - r"Chromium Linux": "chromium-linux", - r"Chromium Win": "chromium-win", - } - - def _port_name_for_builder_name(self, builder_name): - for regexp, port_name in self._builder_name_to_port_name.items(): - if re.match(regexp, builder_name): - return port_name - - def port_for_builder(self, builder_name): - port_name = self._port_name_for_builder_name(builder_name) - assert(port_name) # Need to update _builder_name_to_port_name - port = factory.get(port_name) - assert(port) # Need to update _builder_name_to_port_name - return port - - -class Rebaseline(AbstractDeclarativeCommand): - name = "rebaseline" - help_text = "Replaces local expected.txt files with new results from build bots" - - # FIXME: This should share more code with FailureReason._builder_to_explain - def _builder_to_pull_from(self): - builder_statuses = self._tool.buildbot.builder_statuses() - red_statuses = [status for status in builder_statuses if not status["is_green"]] - print "%s failing" % (pluralize("builder", len(red_statuses))) - builder_choices = [status["name"] for status in red_statuses] - chosen_name = self._tool.user.prompt_with_list("Which builder to pull results from:", builder_choices) - # FIXME: prompt_with_list should really take a set of objects and a set of names and then return the object. - for status in red_statuses: - if status["name"] == chosen_name: - return (self._tool.buildbot.builder_with_name(chosen_name), status["build_number"]) - - def _replace_expectation_with_remote_result(self, local_file, remote_file): - (downloaded_file, headers) = urllib.urlretrieve(remote_file) - shutil.move(downloaded_file, local_file) - - def _tests_to_update(self, build): - parsed_results = build.layout_test_results().parsed_results() - # FIXME: This probably belongs as API on LayoutTestResults - # but .failing_tests() already means something else. - failing_tests = parsed_results[LayoutTestResults.fail_key] - return self._tool.user.prompt_with_list("Which test(s) to rebaseline:", failing_tests, can_choose_multiple=True) - - def _results_url_for_test(self, build, test): - test_base = os.path.splitext(test)[0] - actual_path = test_base + "-actual.txt" - return build.results_url() + "/" + actual_path - - def execute(self, options, args, tool): - builder, build_number = self._builder_to_pull_from() - build = builder.build(build_number) - port = BuilderToPort().port_for_builder(builder.name()) - - for test in self._tests_to_update(build): - results_url = self._results_url_for_test(build, test) - # Port operates with absolute paths. - absolute_path = os.path.join(port.layout_tests_dir(), test) - expected_file = port.expected_filename(absolute_path, ".txt") - print test - self._replace_expectation_with_remote_result(expected_file, results_url) - - # FIXME: We should handle new results too. diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py deleted file mode 100644 index d6582a7..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py +++ /dev/null @@ -1,38 +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.tool.commands.rebaseline import BuilderToPort - - -class BuilderToPortTest(unittest.TestCase): - def test_port_for_builder(self): - converter = BuilderToPort() - port = converter.port_for_builder("Leopard Intel Debug (Tests)") - self.assertEqual(port.name(), "mac-leopard") diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/rebaselineserver.py b/WebKitTools/Scripts/webkitpy/tool/commands/rebaselineserver.py deleted file mode 100644 index 2924a6f..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/rebaselineserver.py +++ /dev/null @@ -1,268 +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. - -"""Starts a local HTTP server which displays layout test failures (given a test -results directory), provides comparisons of expected and actual results (both -images and text) and allows one-click rebaselining of tests.""" -from __future__ import with_statement - -import codecs -import datetime -import mimetypes -import os -import os.path -import shutil -import threading -import time -import urlparse -import BaseHTTPServer - -from optparse import make_option -from wsgiref.handlers import format_date_time - -from webkitpy.common import system -from webkitpy.layout_tests.port import factory -from webkitpy.layout_tests.port.webkit import WebKitPort -from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand -from webkitpy.thirdparty import simplejson - -STATE_NEEDS_REBASELINE = 'needs_rebaseline' -STATE_REBASELINE_FAILED = 'rebaseline_failed' -STATE_REBASELINE_SUCCEEDED = 'rebaseline_succeeded' - -class RebaselineHTTPServer(BaseHTTPServer.HTTPServer): - def __init__(self, httpd_port, results_directory, results_json, platforms_json): - BaseHTTPServer.HTTPServer.__init__(self, ("", httpd_port), RebaselineHTTPRequestHandler) - self.results_directory = results_directory - self.results_json = results_json - self.platforms_json = platforms_json - - -class RebaselineHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): - STATIC_FILE_NAMES = frozenset([ - "index.html", - "loupe.js", - "main.js", - "main.css", - "queue.js", - "util.js", - ]) - - STATIC_FILE_DIRECTORY = os.path.join( - os.path.dirname(__file__), "data", "rebaselineserver") - - def do_GET(self): - self._handle_request() - - def do_POST(self): - self._handle_request() - - def _handle_request(self): - # Parse input. - if "?" in self.path: - path, query_string = self.path.split("?", 1) - self.query = urlparse.parse_qs(query_string) - else: - path = self.path - self.query = {} - function_or_file_name = path[1:] or "index.html" - - # See if a static file matches. - if function_or_file_name in RebaselineHTTPRequestHandler.STATIC_FILE_NAMES: - self._serve_static_file(function_or_file_name) - return - - # See if a class method matches. - function_name = function_or_file_name.replace(".", "_") - if not hasattr(self, function_name): - self.send_error(404, "Unknown function %s" % function_name) - return - if function_name[0] == "_": - self.send_error( - 401, "Not allowed to invoke private or protected methods") - return - function = getattr(self, function_name) - function() - - def _serve_static_file(self, static_path): - self._serve_file(os.path.join( - RebaselineHTTPRequestHandler.STATIC_FILE_DIRECTORY, static_path)) - - def quitquitquit(self): - self.send_response(200) - self.send_header("Content-type", "text/plain") - self.end_headers() - self.wfile.write("Quit.\n") - - # Shutdown has to happen on another thread from the server's thread, - # otherwise there's a deadlock - threading.Thread(target=lambda: self.server.shutdown()).start() - - def test_result(self): - test_name, _ = os.path.splitext(self.query['test'][0]) - mode = self.query['mode'][0] - if mode == 'expected-image': - file_name = test_name + '-expected.png' - elif mode == 'actual-image': - file_name = test_name + '-actual.png' - if mode == 'expected-checksum': - file_name = test_name + '-expected.checksum' - elif mode == 'actual-checksum': - file_name = test_name + '-actual.checksum' - elif mode == 'diff-image': - file_name = test_name + '-diff.png' - if mode == 'expected-text': - file_name = test_name + '-expected.txt' - elif mode == 'actual-text': - file_name = test_name + '-actual.txt' - elif mode == 'diff-text': - file_name = test_name + '-diff.txt' - - file_path = os.path.join(self.server.results_directory, file_name) - - # Let results be cached for 60 seconds, so that they can be pre-fetched - # by the UI - self._serve_file(file_path, cacheable_seconds=60) - - def results_json(self): - self._serve_json(self.server.results_json) - - def platforms_json(self): - self._serve_json(self.server.platforms_json) - - def _serve_json(self, json): - self.send_response(200) - self.send_header('Content-type', 'application/json') - self.end_headers() - simplejson.dump(json, self.wfile) - - def _serve_file(self, file_path, cacheable_seconds=0): - if not os.path.exists(file_path): - self.send_error(404, "File not found") - return - with codecs.open(file_path, "rb") as static_file: - self.send_response(200) - self.send_header("Content-Length", os.path.getsize(file_path)) - mime_type, encoding = mimetypes.guess_type(file_path) - if mime_type: - self.send_header("Content-type", mime_type) - - if cacheable_seconds: - expires_time = (datetime.datetime.now() + - datetime.timedelta(0, cacheable_seconds)) - expires_formatted = format_date_time( - time.mktime(expires_time.timetuple())) - self.send_header("Expires", expires_formatted) - self.end_headers() - - shutil.copyfileobj(static_file, self.wfile) - - -def _get_test_baselines(test_file, layout_tests_directory, platforms, filesystem): - class AllPlatformsPort(WebKitPort): - def __init__(self): - WebKitPort.__init__(self, filesystem=filesystem) - self._platforms_by_directory = dict( - [(self._webkit_baseline_path(p), p) for p in platforms]) - - def baseline_search_path(self): - return self._platforms_by_directory.keys() - - def platform_from_directory(self, directory): - return self._platforms_by_directory[directory] - - all_platforms_port = AllPlatformsPort() - - test_baselines = {} - for baseline_extension in ('.txt', '.checksum', '.png'): - test_path = filesystem.join(layout_tests_directory, test_file) - baselines = all_platforms_port.expected_baselines( - test_path, baseline_extension, all_baselines=True) - for platform_directory, expected_filename in baselines: - if not platform_directory: - continue - if platform_directory == layout_tests_directory: - platform = 'base' - else: - platform = all_platforms_port.platform_from_directory( - platform_directory) - if platform not in test_baselines: - test_baselines[platform] = [] - test_baselines[platform].append(baseline_extension) - - for platform, extensions in test_baselines.items(): - test_baselines[platform] = tuple(extensions) - - return test_baselines - -class RebaselineServer(AbstractDeclarativeCommand): - name = "rebaseline-server" - help_text = __doc__ - argument_names = "/path/to/results/directory" - - def __init__(self): - options = [ - make_option("--httpd-port", action="store", type="int", default=8127, help="Port to use for the the rebaseline HTTP server"), - ] - AbstractDeclarativeCommand.__init__(self, options=options) - - def execute(self, options, args, tool): - results_directory = args[0] - filesystem = system.filesystem.FileSystem() - - print 'Parsing unexpected_results.json...' - results_json_path = filesystem.join( - results_directory, 'unexpected_results.json') - with codecs.open(results_json_path, "r") as results_json_file: - results_json_file = file(results_json_path) - results_json = simplejson.load(results_json_file) - - port = factory.get() - layout_tests_directory = port.layout_tests_dir() - platforms = filesystem.listdir( - filesystem.join(layout_tests_directory, 'platform')) - - print 'Gathering current baselines...' - for test_file, test_json in results_json['tests'].items(): - test_json['state'] = STATE_NEEDS_REBASELINE - test_json['baselines'] = _get_test_baselines( - test_file, layout_tests_directory, platforms, filesystem) - - print "Starting server at http://localhost:%d/" % options.httpd_port - print ("Use the 'Exit' link in the UI, http://localhost:%d/" - "quitquitquit or Ctrl-C to stop") % options.httpd_port - - httpd = RebaselineHTTPServer( - httpd_port=options.httpd_port, - results_directory=results_directory, - results_json=results_json, - platforms_json={ - 'platforms': platforms, - 'defaultPlatform': port.name(), - }) - httpd.serve_forever() diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/rebaselineserver_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/rebaselineserver_unittest.py deleted file mode 100644 index ea52902..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/rebaselineserver_unittest.py +++ /dev/null @@ -1,88 +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.system import filesystem_mock -from webkitpy.layout_tests.port import base -from webkitpy.tool.commands import rebaselineserver - - -class GetBaselinesTest(unittest.TestCase): - def test_no_baselines(self): - self._assertBaselines( - test_files=(), - test='fast/missing.html', - expected_baselines={}) - - def test_text_baselines(self): - self._assertBaselines( - test_files=( - 'fast/text-expected.txt', - 'platform/mac/fast/text-expected.txt', - ), - test='fast/text.html', - expected_baselines={'mac': ('.txt',), 'base': ('.txt',)}) - - def test_image_and_text_baselines(self): - self._assertBaselines( - test_files=( - 'fast/image-expected.txt', - 'platform/mac/fast/image-expected.png', - 'platform/mac/fast/image-expected.checksum', - 'platform/win/fast/image-expected.png', - 'platform/win/fast/image-expected.checksum', - ), - test='fast/image.html', - expected_baselines={ - 'base': ('.txt',), - 'mac': ('.checksum', '.png'), - 'win': ('.checksum', '.png'), - }) - - def test_extra_baselines(self): - self._assertBaselines( - test_files=( - 'fast/text-expected.txt', - 'platform/nosuchplatform/fast/text-expected.txt', - ), - test='fast/text.html', - expected_baselines={'base': ('.txt',)}) - - def _assertBaselines(self, test_files, test, expected_baselines): - layout_tests_directory = base.Port().layout_tests_dir() - mock_filesystem = filesystem_mock.MockFileSystem() - for file in test_files + (test,): - file_path = mock_filesystem.join(layout_tests_directory, file) - mock_filesystem.files[file_path] = '' - actual_baselines = rebaselineserver._get_test_baselines( - test, - layout_tests_directory, - ('mac', 'win', 'linux'), - mock_filesystem) - self.assertEqual(actual_baselines, expected_baselines) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py b/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py deleted file mode 100644 index 145f485..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot.py +++ /dev/null @@ -1,106 +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 - -from webkitpy.common.system.deprecated_logging import log -from webkitpy.common.config.ports import WebKitPort -from webkitpy.tool.bot.sheriff import Sheriff -from webkitpy.tool.bot.sheriffircbot import SheriffIRCBot -from webkitpy.tool.commands.queues import AbstractQueue -from webkitpy.tool.commands.stepsequence import StepSequenceErrorHandler - - -class SheriffBot(AbstractQueue, StepSequenceErrorHandler): - name = "sheriff-bot" - watchers = AbstractQueue.watchers + [ - "abarth@webkit.org", - "eric@webkit.org", - ] - - def _update(self): - self.run_webkit_patch(["update", "--force-clean", "--quiet"]) - - # AbstractQueue methods - - def begin_work_queue(self): - AbstractQueue.begin_work_queue(self) - self._sheriff = Sheriff(self._tool, self) - self._irc_bot = SheriffIRCBot(self._tool, self._sheriff) - self._tool.ensure_irc_connected(self._irc_bot.irc_delegate()) - - def work_item_log_path(self, failure_map): - return None - - def _is_old_failure(self, revision): - return self._tool.status_server.svn_revision(revision) - - def next_work_item(self): - self._irc_bot.process_pending_messages() - self._update() - - # FIXME: We need to figure out how to provoke_flaky_builders. - - failure_map = self._tool.buildbot.failure_map() - failure_map.filter_out_old_failures(self._is_old_failure) - if failure_map.is_empty(): - return None - return failure_map - - def should_proceed_with_work_item(self, failure_map): - # Currently, we don't have any reasons not to proceed with work items. - return True - - def process_work_item(self, failure_map): - failing_revisions = failure_map.failing_revisions() - for revision in failing_revisions: - builders = failure_map.builders_failing_for(revision) - tests = failure_map.tests_failing_for(revision) - try: - commit_info = self._tool.checkout().commit_info_for_revision(revision) - if not commit_info: - print "FAILED to fetch CommitInfo for r%s, likely missing ChangeLog" % revision - continue - self._sheriff.post_irc_warning(commit_info, builders) - self._sheriff.post_blame_comment_on_bug(commit_info, builders, tests) - - finally: - for builder in builders: - self._tool.status_server.update_svn_revision(revision, builder.name()) - return True - - def handle_unexpected_error(self, failure_map, message): - log(message) - - # StepSequenceErrorHandler methods - - @classmethod - def handle_script_error(cls, tool, state, script_error): - # Ideally we would post some information to IRC about what went wrong - # here, but we don't have the IRC password in the child process. - pass diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py deleted file mode 100644 index 4db463e..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/sheriffbot_unittest.py +++ /dev/null @@ -1,57 +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 - -from webkitpy.tool.commands.queuestest import QueuesTest -from webkitpy.tool.commands.sheriffbot import SheriffBot -from webkitpy.tool.mocktool import * - - -class SheriffBotTest(QueuesTest): - builder1 = MockBuilder("Builder1") - builder2 = MockBuilder("Builder2") - - def test_sheriff_bot(self): - tool = MockTool() - mock_work_item = MockFailureMap(tool.buildbot) - expected_stderr = { - "begin_work_queue": self._default_begin_work_queue_stderr("sheriff-bot", tool.scm().checkout_root), - "next_work_item": "", - "process_work_item": """MOCK: irc.post: abarth, darin, eseidel: http://trac.webkit.org/changeset/29837 might have broken Builder1 -MOCK bug comment: bug_id=42, cc=['abarth@webkit.org', 'eric@webkit.org'] ---- Begin comment --- -http://trac.webkit.org/changeset/29837 might have broken Builder1 -The following tests are not passing: -mock-test-1 ---- End comment --- - -""", - "handle_unexpected_error": "Mock error message\n" - } - self.assert_queue_outputs(SheriffBot(), work_item=mock_work_item, expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/stepsequence.py b/WebKitTools/Scripts/webkitpy/tool/commands/stepsequence.py deleted file mode 100644 index be2ed4c..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/stepsequence.py +++ /dev/null @@ -1,83 +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 webkitpy.tool.steps as steps - -from webkitpy.common.system.executive import ScriptError -from webkitpy.common.checkout.scm import CheckoutNeedsUpdate -from webkitpy.tool.bot.queueengine import QueueEngine -from webkitpy.common.system.deprecated_logging import log - - -class StepSequenceErrorHandler(): - @classmethod - def handle_script_error(cls, tool, patch, script_error): - raise NotImplementedError, "subclasses must implement" - - @classmethod - def handle_checkout_needs_update(cls, tool, state, options, error): - raise NotImplementedError, "subclasses must implement" - - -class StepSequence(object): - def __init__(self, steps): - self._steps = steps or [] - - def options(self): - collected_options = [ - steps.Options.parent_command, - steps.Options.quiet, - ] - for step in self._steps: - collected_options = collected_options + step.options() - # Remove duplicates. - collected_options = sorted(set(collected_options)) - return collected_options - - def _run(self, tool, options, state): - for step in self._steps: - step(tool, options).run(state) - - def run_and_handle_errors(self, tool, options, state=None): - if not state: - state = {} - try: - self._run(tool, options, state) - except CheckoutNeedsUpdate, e: - log("Commit failed because the checkout is out of date. Please update and try again.") - if options.parent_command: - command = tool.command_by_name(options.parent_command) - command.handle_checkout_needs_update(tool, state, options, e) - QueueEngine.exit_after_handled_error(e) - except ScriptError, e: - if not options.quiet: - log(e.message_with_output()) - if options.parent_command: - command = tool.command_by_name(options.parent_command) - command.handle_script_error(tool, state, e) - QueueEngine.exit_after_handled_error(e) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/upload.py b/WebKitTools/Scripts/webkitpy/tool/commands/upload.py deleted file mode 100644 index e12c8e2..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/upload.py +++ /dev/null @@ -1,483 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2009, 2010 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. - -import os -import re -import sys - -from optparse import make_option - -import webkitpy.tool.steps as steps - -from webkitpy.common.config.committers import CommitterList -from webkitpy.common.net.bugzilla import parse_bug_id -from webkitpy.common.system.user import User -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand -from webkitpy.tool.grammar import pluralize, join_with_separators -from webkitpy.tool.comments import bug_comment_from_svn_revision -from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand -from webkitpy.common.system.deprecated_logging import error, log - - -class CommitMessageForCurrentDiff(AbstractDeclarativeCommand): - name = "commit-message" - help_text = "Print a commit message suitable for the uncommitted changes" - - def __init__(self): - options = [ - steps.Options.git_commit, - ] - AbstractDeclarativeCommand.__init__(self, options=options) - - def execute(self, options, args, tool): - # This command is a useful test to make sure commit_message_for_this_commit - # always returns the right value regardless of the current working directory. - print "%s" % tool.checkout().commit_message_for_this_commit(options.git_commit).message() - - -class CleanPendingCommit(AbstractDeclarativeCommand): - name = "clean-pending-commit" - help_text = "Clear r+ on obsolete patches so they do not appear in the pending-commit list." - - # NOTE: This was designed to be generic, but right now we're only processing patches from the pending-commit list, so only r+ matters. - def _flags_to_clear_on_patch(self, patch): - if not patch.is_obsolete(): - return None - what_was_cleared = [] - if patch.review() == "+": - if patch.reviewer(): - what_was_cleared.append("%s's review+" % patch.reviewer().full_name) - else: - what_was_cleared.append("review+") - return join_with_separators(what_was_cleared) - - def execute(self, options, args, tool): - committers = CommitterList() - for bug_id in tool.bugs.queries.fetch_bug_ids_from_pending_commit_list(): - bug = self._tool.bugs.fetch_bug(bug_id) - patches = bug.patches(include_obsolete=True) - for patch in patches: - flags_to_clear = self._flags_to_clear_on_patch(patch) - if not flags_to_clear: - continue - message = "Cleared %s from obsolete attachment %s so that this bug does not appear in http://webkit.org/pending-commit." % (flags_to_clear, patch.id()) - self._tool.bugs.obsolete_attachment(patch.id(), message) - - -# FIXME: This should be share more logic with AssignToCommitter and CleanPendingCommit -class CleanReviewQueue(AbstractDeclarativeCommand): - name = "clean-review-queue" - help_text = "Clear r? on obsolete patches so they do not appear in the pending-commit list." - - def execute(self, options, args, tool): - queue_url = "http://webkit.org/pending-review" - # We do this inefficient dance to be more like webkit.org/pending-review - # bugs.queries.fetch_bug_ids_from_review_queue() doesn't return - # closed bugs, but folks using /pending-review will see them. :( - for patch_id in tool.bugs.queries.fetch_attachment_ids_from_review_queue(): - patch = self._tool.bugs.fetch_attachment(patch_id) - if not patch.review() == "?": - continue - attachment_obsolete_modifier = "" - if patch.is_obsolete(): - attachment_obsolete_modifier = "obsolete " - elif patch.bug().is_closed(): - bug_closed_explanation = " If you would like this patch reviewed, please attach it to a new bug (or re-open this bug before marking it for review again)." - else: - # Neither the patch was obsolete or the bug was closed, next patch... - continue - message = "Cleared review? from %sattachment %s so that this bug does not appear in %s.%s" % (attachment_obsolete_modifier, patch.id(), queue_url, bug_closed_explanation) - self._tool.bugs.obsolete_attachment(patch.id(), message) - - -class AssignToCommitter(AbstractDeclarativeCommand): - name = "assign-to-committer" - help_text = "Assign bug to whoever attached the most recent r+'d patch" - - def _patches_have_commiters(self, reviewed_patches): - for patch in reviewed_patches: - if not patch.committer(): - return False - return True - - def _assign_bug_to_last_patch_attacher(self, bug_id): - committers = CommitterList() - bug = self._tool.bugs.fetch_bug(bug_id) - if not bug.is_unassigned(): - assigned_to_email = bug.assigned_to_email() - log("Bug %s is already assigned to %s (%s)." % (bug_id, assigned_to_email, committers.committer_by_email(assigned_to_email))) - return - - reviewed_patches = bug.reviewed_patches() - if not reviewed_patches: - log("Bug %s has no non-obsolete patches, ignoring." % bug_id) - return - - # We only need to do anything with this bug if one of the r+'d patches does not have a valid committer (cq+ set). - if self._patches_have_commiters(reviewed_patches): - log("All reviewed patches on bug %s already have commit-queue+, ignoring." % bug_id) - return - - latest_patch = reviewed_patches[-1] - attacher_email = latest_patch.attacher_email() - committer = committers.committer_by_email(attacher_email) - if not committer: - log("Attacher %s is not a committer. Bug %s likely needs commit-queue+." % (attacher_email, bug_id)) - return - - reassign_message = "Attachment %s was posted by a committer and has review+, assigning to %s for commit." % (latest_patch.id(), committer.full_name) - self._tool.bugs.reassign_bug(bug_id, committer.bugzilla_email(), reassign_message) - - def execute(self, options, args, tool): - for bug_id in tool.bugs.queries.fetch_bug_ids_from_pending_commit_list(): - self._assign_bug_to_last_patch_attacher(bug_id) - - -class ObsoleteAttachments(AbstractSequencedCommand): - name = "obsolete-attachments" - help_text = "Mark all attachments on a bug as obsolete" - argument_names = "BUGID" - steps = [ - steps.ObsoletePatches, - ] - - def _prepare_state(self, options, args, tool): - return { "bug_id" : args[0] } - - -class AbstractPatchUploadingCommand(AbstractSequencedCommand): - def _bug_id(self, options, args, tool, state): - # Perfer a bug id passed as an argument over a bug url in the diff (i.e. ChangeLogs). - bug_id = args and args[0] - if not bug_id: - changed_files = self._tool.scm().changed_files(options.git_commit) - state["changed_files"] = changed_files - bug_id = tool.checkout().bug_id_for_this_commit(options.git_commit, changed_files) - return bug_id - - def _prepare_state(self, options, args, tool): - state = {} - state["bug_id"] = self._bug_id(options, args, tool, state) - if not state["bug_id"]: - error("No bug id passed and no bug url found in ChangeLogs.") - return state - - -class Post(AbstractPatchUploadingCommand): - name = "post" - help_text = "Attach the current working directory diff to a bug as a patch file" - argument_names = "[BUGID]" - steps = [ - steps.CheckStyle, - steps.ConfirmDiff, - steps.ObsoletePatches, - steps.SuggestReviewers, - steps.PostDiff, - ] - - -class LandSafely(AbstractPatchUploadingCommand): - name = "land-safely" - help_text = "Land the current diff via the commit-queue" - argument_names = "[BUGID]" - long_help = """land-safely updates the ChangeLog with the reviewer listed - in bugs.webkit.org for BUGID (or the bug ID detected from the ChangeLog). - The command then uploads the current diff to the bug and marks it for - commit by the commit-queue.""" - show_in_main_help = True - steps = [ - steps.UpdateChangeLogsWithReviewer, - steps.ObsoletePatches, - steps.PostDiffForCommit, - ] - - -class Prepare(AbstractSequencedCommand): - name = "prepare" - help_text = "Creates a bug (or prompts for an existing bug) and prepares the ChangeLogs" - argument_names = "[BUGID]" - steps = [ - steps.PromptForBugOrTitle, - steps.CreateBug, - steps.PrepareChangeLog, - ] - - def _prepare_state(self, options, args, tool): - bug_id = args and args[0] - return { "bug_id" : bug_id } - - -class Upload(AbstractPatchUploadingCommand): - name = "upload" - help_text = "Automates the process of uploading a patch for review" - argument_names = "[BUGID]" - show_in_main_help = True - steps = [ - steps.CheckStyle, - steps.PromptForBugOrTitle, - steps.CreateBug, - steps.PrepareChangeLog, - steps.EditChangeLog, - steps.ConfirmDiff, - steps.ObsoletePatches, - steps.SuggestReviewers, - steps.PostDiff, - ] - long_help = """upload uploads the current diff to bugs.webkit.org. - If no bug id is provided, upload will create a bug. - If the current diff does not have a ChangeLog, upload - will prepare a ChangeLog. Once a patch is read, upload - will open the ChangeLogs for editing using the command in the - EDITOR environment variable and will display the diff using the - command in the PAGER environment variable.""" - - def _prepare_state(self, options, args, tool): - state = {} - state["bug_id"] = self._bug_id(options, args, tool, state) - return state - - -class EditChangeLogs(AbstractSequencedCommand): - name = "edit-changelogs" - help_text = "Opens modified ChangeLogs in $EDITOR" - show_in_main_help = True - steps = [ - steps.EditChangeLog, - ] - - -class PostCommits(AbstractDeclarativeCommand): - name = "post-commits" - help_text = "Attach a range of local commits to bugs as patch files" - argument_names = "COMMITISH" - - def __init__(self): - options = [ - make_option("-b", "--bug-id", action="store", type="string", dest="bug_id", help="Specify bug id if no URL is provided in the commit log."), - make_option("--add-log-as-comment", action="store_true", dest="add_log_as_comment", default=False, help="Add commit log message as a comment when uploading the patch."), - make_option("-m", "--description", action="store", type="string", dest="description", help="Description string for the attachment (default: description from commit message)"), - steps.Options.obsolete_patches, - steps.Options.review, - steps.Options.request_commit, - ] - AbstractDeclarativeCommand.__init__(self, options=options, requires_local_commits=True) - - def _comment_text_for_commit(self, options, commit_message, tool, commit_id): - comment_text = None - if (options.add_log_as_comment): - comment_text = commit_message.body(lstrip=True) - comment_text += "---\n" - comment_text += tool.scm().files_changed_summary_for_commit(commit_id) - return comment_text - - def execute(self, options, args, tool): - commit_ids = tool.scm().commit_ids_from_commitish_arguments(args) - if len(commit_ids) > 10: # We could lower this limit, 10 is too many for one bug as-is. - error("webkit-patch does not support attaching %s at once. Are you sure you passed the right commit range?" % (pluralize("patch", len(commit_ids)))) - - have_obsoleted_patches = set() - for commit_id in commit_ids: - commit_message = tool.scm().commit_message_for_local_commit(commit_id) - - # Prefer --bug-id=, then a bug url in the commit message, then a bug url in the entire commit diff (i.e. ChangeLogs). - bug_id = options.bug_id or parse_bug_id(commit_message.message()) or parse_bug_id(tool.scm().create_patch(git_commit=commit_id)) - if not bug_id: - log("Skipping %s: No bug id found in commit or specified with --bug-id." % commit_id) - continue - - if options.obsolete_patches and bug_id not in have_obsoleted_patches: - state = { "bug_id": bug_id } - steps.ObsoletePatches(tool, options).run(state) - have_obsoleted_patches.add(bug_id) - - diff = tool.scm().create_patch(git_commit=commit_id) - description = options.description or commit_message.description(lstrip=True, strip_url=True) - comment_text = self._comment_text_for_commit(options, commit_message, tool, commit_id) - tool.bugs.add_patch_to_bug(bug_id, diff, description, comment_text, mark_for_review=options.review, mark_for_commit_queue=options.request_commit) - - -# FIXME: This command needs to be brought into the modern age with steps and CommitInfo. -class MarkBugFixed(AbstractDeclarativeCommand): - name = "mark-bug-fixed" - help_text = "Mark the specified bug as fixed" - argument_names = "[SVN_REVISION]" - def __init__(self): - options = [ - make_option("--bug-id", action="store", type="string", dest="bug_id", help="Specify bug id if no URL is provided in the commit log."), - make_option("--comment", action="store", type="string", dest="comment", help="Text to include in bug comment."), - make_option("--open", action="store_true", default=False, dest="open_bug", help="Open bug in default web browser (Mac only)."), - make_option("--update-only", action="store_true", default=False, dest="update_only", help="Add comment to the bug, but do not close it."), - ] - AbstractDeclarativeCommand.__init__(self, options=options) - - # FIXME: We should be using checkout().changelog_entries_for_revision(...) instead here. - def _fetch_commit_log(self, tool, svn_revision): - if not svn_revision: - return tool.scm().last_svn_commit_log() - return tool.scm().svn_commit_log(svn_revision) - - def _determine_bug_id_and_svn_revision(self, tool, bug_id, svn_revision): - commit_log = self._fetch_commit_log(tool, svn_revision) - - if not bug_id: - bug_id = parse_bug_id(commit_log) - - if not svn_revision: - match = re.search("^r(?P<svn_revision>\d+) \|", commit_log, re.MULTILINE) - if match: - svn_revision = match.group('svn_revision') - - if not bug_id or not svn_revision: - not_found = [] - if not bug_id: - not_found.append("bug id") - if not svn_revision: - not_found.append("svn revision") - error("Could not find %s on command-line or in %s." - % (" or ".join(not_found), "r%s" % svn_revision if svn_revision else "last commit")) - - return (bug_id, svn_revision) - - def execute(self, options, args, tool): - bug_id = options.bug_id - - svn_revision = args and args[0] - if svn_revision: - if re.match("^r[0-9]+$", svn_revision, re.IGNORECASE): - svn_revision = svn_revision[1:] - if not re.match("^[0-9]+$", svn_revision): - error("Invalid svn revision: '%s'" % svn_revision) - - needs_prompt = False - if not bug_id or not svn_revision: - needs_prompt = True - (bug_id, svn_revision) = self._determine_bug_id_and_svn_revision(tool, bug_id, svn_revision) - - log("Bug: <%s> %s" % (tool.bugs.bug_url_for_bug_id(bug_id), tool.bugs.fetch_bug_dictionary(bug_id)["title"])) - log("Revision: %s" % svn_revision) - - if options.open_bug: - tool.user.open_url(tool.bugs.bug_url_for_bug_id(bug_id)) - - if needs_prompt: - if not tool.user.confirm("Is this correct?"): - exit(1) - - bug_comment = bug_comment_from_svn_revision(svn_revision) - if options.comment: - bug_comment = "%s\n\n%s" % (options.comment, bug_comment) - - if options.update_only: - log("Adding comment to Bug %s." % bug_id) - tool.bugs.post_comment_to_bug(bug_id, bug_comment) - else: - log("Adding comment to Bug %s and marking as Resolved/Fixed." % bug_id) - tool.bugs.close_bug_as_fixed(bug_id, bug_comment) - - -# FIXME: Requires unit test. Blocking issue: too complex for now. -class CreateBug(AbstractDeclarativeCommand): - name = "create-bug" - help_text = "Create a bug from local changes or local commits" - argument_names = "[COMMITISH]" - - def __init__(self): - options = [ - steps.Options.cc, - steps.Options.component, - make_option("--no-prompt", action="store_false", dest="prompt", default=True, help="Do not prompt for bug title and comment; use commit log instead."), - make_option("--no-review", action="store_false", dest="review", default=True, help="Do not mark the patch for review."), - make_option("--request-commit", action="store_true", dest="request_commit", default=False, help="Mark the patch as needing auto-commit after review."), - ] - AbstractDeclarativeCommand.__init__(self, options=options) - - def create_bug_from_commit(self, options, args, tool): - commit_ids = tool.scm().commit_ids_from_commitish_arguments(args) - if len(commit_ids) > 3: - error("Are you sure you want to create one bug with %s patches?" % len(commit_ids)) - - commit_id = commit_ids[0] - - bug_title = "" - comment_text = "" - if options.prompt: - (bug_title, comment_text) = self.prompt_for_bug_title_and_comment() - else: - commit_message = tool.scm().commit_message_for_local_commit(commit_id) - bug_title = commit_message.description(lstrip=True, strip_url=True) - comment_text = commit_message.body(lstrip=True) - comment_text += "---\n" - comment_text += tool.scm().files_changed_summary_for_commit(commit_id) - - diff = tool.scm().create_patch(git_commit=commit_id) - bug_id = tool.bugs.create_bug(bug_title, comment_text, options.component, diff, "Patch", cc=options.cc, mark_for_review=options.review, mark_for_commit_queue=options.request_commit) - - if bug_id and len(commit_ids) > 1: - options.bug_id = bug_id - options.obsolete_patches = False - # FIXME: We should pass through --no-comment switch as well. - PostCommits.execute(self, options, commit_ids[1:], tool) - - def create_bug_from_patch(self, options, args, tool): - bug_title = "" - comment_text = "" - if options.prompt: - (bug_title, comment_text) = self.prompt_for_bug_title_and_comment() - else: - commit_message = tool.checkout().commit_message_for_this_commit(options.git_commit) - bug_title = commit_message.description(lstrip=True, strip_url=True) - comment_text = commit_message.body(lstrip=True) - - diff = tool.scm().create_patch(options.git_commit) - bug_id = tool.bugs.create_bug(bug_title, comment_text, options.component, diff, "Patch", cc=options.cc, mark_for_review=options.review, mark_for_commit_queue=options.request_commit) - - def prompt_for_bug_title_and_comment(self): - bug_title = User.prompt("Bug title: ") - print "Bug comment (hit ^D on blank line to end):" - lines = sys.stdin.readlines() - try: - sys.stdin.seek(0, os.SEEK_END) - except IOError: - # Cygwin raises an Illegal Seek (errno 29) exception when the above - # seek() call is made. Ignoring it seems to cause no harm. - # FIXME: Figure out a way to get avoid the exception in the first - # place. - pass - comment_text = "".join(lines) - return (bug_title, comment_text) - - def execute(self, options, args, tool): - if len(args): - if (not tool.scm().supports_local_commits()): - error("Extra arguments not supported; patch is taken from working directory.") - self.create_bug_from_commit(options, args, tool) - else: - self.create_bug_from_patch(options, args, tool) diff --git a/WebKitTools/Scripts/webkitpy/tool/commands/upload_unittest.py b/WebKitTools/Scripts/webkitpy/tool/commands/upload_unittest.py deleted file mode 100644 index bd1fbd6..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/commands/upload_unittest.py +++ /dev/null @@ -1,117 +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.thirdparty.mock import Mock -from webkitpy.tool.commands.commandtest import CommandsTest -from webkitpy.tool.commands.upload import * -from webkitpy.tool.mocktool import MockOptions, MockTool - -class UploadCommandsTest(CommandsTest): - def test_commit_message_for_current_diff(self): - tool = MockTool() - expected_stdout = "This is a fake commit message that is at least 50 characters.\n" - self.assert_execute_outputs(CommitMessageForCurrentDiff(), [], expected_stdout=expected_stdout, tool=tool) - - def test_clean_pending_commit(self): - self.assert_execute_outputs(CleanPendingCommit(), []) - - def test_assign_to_committer(self): - tool = MockTool() - expected_stderr = "Warning, attachment 128 on bug 42 has invalid committer (non-committer@example.com)\nBug 77 is already assigned to foo@foo.com (None).\nBug 76 has no non-obsolete patches, ignoring.\n" - self.assert_execute_outputs(AssignToCommitter(), [], expected_stderr=expected_stderr, tool=tool) - tool.bugs.reassign_bug.assert_called_with(42, "eric@webkit.org", "Attachment 128 was posted by a committer and has review+, assigning to Eric Seidel for commit.") - - def test_obsolete_attachments(self): - expected_stderr = "Obsoleting 2 old patches on bug 42\n" - self.assert_execute_outputs(ObsoleteAttachments(), [42], expected_stderr=expected_stderr) - - def test_post(self): - options = MockOptions() - options.cc = None - options.check_style = True - options.comment = None - options.description = "MOCK description" - options.request_commit = False - options.review = True - options.suggest_reviewers = False - expected_stderr = """Running check-webkit-style -MOCK: user.open_url: file://... -Obsoleting 2 old patches on bug 42 -MOCK add_patch_to_bug: bug_id=42, description=MOCK description, mark_for_review=True, mark_for_commit_queue=False, mark_for_landing=False --- Begin comment -- -None --- End comment -- -MOCK: user.open_url: http://example.com/42 -""" - expected_stdout = "Was that diff correct?\n" - self.assert_execute_outputs(Post(), [42], options=options, expected_stdout=expected_stdout, expected_stderr=expected_stderr) - - def test_land_safely(self): - expected_stderr = "Obsoleting 2 old patches on bug 42\nMOCK add_patch_to_bug: bug_id=42, description=Patch for landing, mark_for_review=False, mark_for_commit_queue=False, mark_for_landing=True\n-- Begin comment --\nNone\n-- End comment --\n" - self.assert_execute_outputs(LandSafely(), [42], expected_stderr=expected_stderr) - - def test_prepare_diff_with_arg(self): - self.assert_execute_outputs(Prepare(), [42]) - - def test_prepare(self): - expected_stderr = "MOCK create_bug\nbug_title: Mock user response\nbug_description: Mock user response\n" - self.assert_execute_outputs(Prepare(), [], expected_stderr=expected_stderr) - - def test_upload(self): - options = MockOptions() - options.cc = None - options.check_style = True - options.comment = None - options.description = "MOCK description" - options.request_commit = False - options.review = True - options.suggest_reviewers = False - expected_stderr = """Running check-webkit-style -MOCK: user.open_url: file://... -Obsoleting 2 old patches on bug 42 -MOCK add_patch_to_bug: bug_id=42, description=MOCK description, mark_for_review=True, mark_for_commit_queue=False, mark_for_landing=False --- Begin comment -- -None --- End comment -- -MOCK: user.open_url: http://example.com/42 -""" - expected_stdout = "Was that diff correct?\n" - self.assert_execute_outputs(Upload(), [42], options=options, expected_stdout=expected_stdout, expected_stderr=expected_stderr) - - def test_mark_bug_fixed(self): - tool = MockTool() - tool._scm.last_svn_commit_log = lambda: "r9876 |" - options = Mock() - options.bug_id = 42 - options.comment = "MOCK comment" - expected_stderr = "Bug: <http://example.com/42> Bug with two r+'d and cq+'d patches, one of which has an invalid commit-queue setter.\nRevision: 9876\nMOCK: user.open_url: http://example.com/42\nAdding comment to Bug 42.\nMOCK bug comment: bug_id=42, cc=None\n--- Begin comment ---\nMOCK comment\n\nCommitted r9876: <http://trac.webkit.org/changeset/9876>\n--- End comment ---\n\n" - expected_stdout = "Is this correct?\n" - self.assert_execute_outputs(MarkBugFixed(), [], expected_stdout=expected_stdout, expected_stderr=expected_stderr, tool=tool, options=options) - - def test_edit_changelog(self): - self.assert_execute_outputs(EditChangeLogs(), []) diff --git a/WebKitTools/Scripts/webkitpy/tool/comments.py b/WebKitTools/Scripts/webkitpy/tool/comments.py deleted file mode 100755 index 83f2be8..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/comments.py +++ /dev/null @@ -1,43 +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. -# -# A tool for automating dealing with bugzilla, posting patches, committing -# patches, etc. - -from webkitpy.common.checkout.changelog import view_source_url - - -def bug_comment_from_svn_revision(svn_revision): - return "Committed r%s: <%s>" % (svn_revision, - view_source_url(svn_revision)) - - -def bug_comment_from_commit_text(scm, commit_text): - svn_revision = scm.svn_revision_from_commit_text(commit_text) - return bug_comment_from_svn_revision(svn_revision) diff --git a/WebKitTools/Scripts/webkitpy/tool/grammar.py b/WebKitTools/Scripts/webkitpy/tool/grammar.py deleted file mode 100644 index 8db9826..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/grammar.py +++ /dev/null @@ -1,54 +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. - -import re - - -def plural(noun): - # This is a dumb plural() implementation that is just enough for our uses. - if re.search("h$", noun): - return noun + "es" - else: - return noun + "s" - - -def pluralize(noun, count): - if count != 1: - noun = plural(noun) - return "%d %s" % (count, noun) - - -def join_with_separators(list_of_strings, separator=', ', only_two_separator=" and ", last_separator=', and '): - if not list_of_strings: - return "" - if len(list_of_strings) == 1: - return list_of_strings[0] - if len(list_of_strings) == 2: - return only_two_separator.join(list_of_strings) - return "%s%s%s" % (separator.join(list_of_strings[:-1]), last_separator, list_of_strings[-1]) diff --git a/WebKitTools/Scripts/webkitpy/tool/grammar_unittest.py b/WebKitTools/Scripts/webkitpy/tool/grammar_unittest.py deleted file mode 100644 index cab71db..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/grammar_unittest.py +++ /dev/null @@ -1,41 +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.tool.grammar import join_with_separators - -class GrammarTest(unittest.TestCase): - - def test_join_with_separators(self): - self.assertEqual(join_with_separators(["one"]), "one") - self.assertEqual(join_with_separators(["one", "two"]), "one and two") - self.assertEqual(join_with_separators(["one", "two", "three"]), "one, two, and three") - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/tool/main.py b/WebKitTools/Scripts/webkitpy/tool/main.py deleted file mode 100755 index 7b1d7f3..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/main.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright (c) 2010 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. -# -# A tool for automating dealing with bugzilla, posting patches, committing patches, etc. - -from optparse import make_option -import os -import threading - -from webkitpy.common.checkout.api import Checkout -from webkitpy.common.checkout.scm import default_scm -from webkitpy.common.config.ports import WebKitPort -from webkitpy.common.net.bugzilla import Bugzilla -from webkitpy.common.net.buildbot import BuildBot -from webkitpy.common.net.irc.ircproxy import IRCProxy -from webkitpy.common.net.statusserver import StatusServer -from webkitpy.common.system.executive import Executive -from webkitpy.common.system.user import User -from webkitpy.layout_tests import port -from webkitpy.tool.multicommandtool import MultiCommandTool -import webkitpy.tool.commands as commands - - -class WebKitPatch(MultiCommandTool): - global_options = [ - make_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="enable all logging"), - make_option("--dry-run", action="store_true", dest="dry_run", default=False, help="do not touch remote servers"), - make_option("--status-host", action="store", dest="status_host", type="string", help="Hostname (e.g. localhost or commit.webkit.org) where status updates should be posted."), - make_option("--bot-id", action="store", dest="bot_id", type="string", help="Identifier for this bot (if multiple bots are running for a queue)"), - make_option("--irc-password", action="store", dest="irc_password", type="string", help="Password to use when communicating via IRC."), - make_option("--port", action="store", dest="port", default=None, help="Specify a port (e.g., mac, qt, gtk, ...)."), - ] - - def __init__(self, path): - MultiCommandTool.__init__(self) - - self._path = path - self.wakeup_event = threading.Event() - self.bugs = Bugzilla() - self.buildbot = BuildBot() - self.executive = Executive() - self._irc = None - self._port = None - self.user = User() - self._scm = None - self._checkout = None - self.status_server = StatusServer() - self.port_factory = port.factory - - def scm(self): - # Lazily initialize SCM to not error-out before command line parsing (or when running non-scm commands). - if not self._scm: - self._scm = default_scm() - return self._scm - - def checkout(self): - if not self._checkout: - self._checkout = Checkout(self.scm()) - return self._checkout - - def port(self): - return self._port - - def ensure_irc_connected(self, irc_delegate): - if not self._irc: - self._irc = IRCProxy(irc_delegate) - - def irc(self): - # We don't automatically construct IRCProxy here because constructing - # IRCProxy actually connects to IRC. We want clients to explicitly - # connect to IRC. - return self._irc - - def path(self): - return self._path - - def command_completed(self): - if self._irc: - self._irc.disconnect() - - def should_show_in_main_help(self, command): - if not command.show_in_main_help: - return False - if command.requires_local_commits: - return self.scm().supports_local_commits() - return True - - # FIXME: This may be unnecessary since we pass global options to all commands during execute() as well. - def handle_global_options(self, options): - self._options = options - if options.dry_run: - self.scm().dryrun = True - self.bugs.dryrun = True - if options.status_host: - self.status_server.set_host(options.status_host) - if options.bot_id: - self.status_server.set_bot_id(options.bot_id) - if options.irc_password: - self.irc_password = options.irc_password - # If options.port is None, we'll get the default port for this platform. - self._port = WebKitPort.port(options.port) - - def should_execute_command(self, command): - if command.requires_local_commits and not self.scm().supports_local_commits(): - failure_reason = "%s requires local commits using %s in %s." % (command.name, self.scm().display_name(), self.scm().checkout_root) - return (False, failure_reason) - return (True, None) diff --git a/WebKitTools/Scripts/webkitpy/tool/mocktool.py b/WebKitTools/Scripts/webkitpy/tool/mocktool.py deleted file mode 100644 index 719f9b1..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/mocktool.py +++ /dev/null @@ -1,676 +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 threading - -from webkitpy.common.config.committers import CommitterList, Reviewer -from webkitpy.common.checkout.commitinfo import CommitInfo -from webkitpy.common.checkout.scm import CommitMessage -from webkitpy.common.net.bugzilla import Bug, Attachment -from webkitpy.thirdparty.mock import Mock -from webkitpy.common.system.deprecated_logging import log - - -def _id_to_object_dictionary(*objects): - dictionary = {} - for thing in objects: - dictionary[thing["id"]] = thing - return dictionary - -# Testing - -# FIXME: The ids should be 1, 2, 3 instead of crazy numbers. - - -_patch1 = { - "id": 197, - "bug_id": 42, - "url": "http://example.com/197", - "name": "Patch1", - "is_obsolete": False, - "is_patch": True, - "review": "+", - "reviewer_email": "foo@bar.com", - "commit-queue": "+", - "committer_email": "foo@bar.com", - "attacher_email": "Contributer1", -} - - -_patch2 = { - "id": 128, - "bug_id": 42, - "url": "http://example.com/128", - "name": "Patch2", - "is_obsolete": False, - "is_patch": True, - "review": "+", - "reviewer_email": "foo@bar.com", - "commit-queue": "+", - "committer_email": "non-committer@example.com", - "attacher_email": "eric@webkit.org", -} - - -_patch3 = { - "id": 103, - "bug_id": 75, - "url": "http://example.com/103", - "name": "Patch3", - "is_obsolete": False, - "is_patch": True, - "review": "?", - "attacher_email": "eric@webkit.org", -} - - -_patch4 = { - "id": 104, - "bug_id": 77, - "url": "http://example.com/103", - "name": "Patch3", - "is_obsolete": False, - "is_patch": True, - "review": "+", - "commit-queue": "?", - "reviewer_email": "foo@bar.com", - "attacher_email": "Contributer2", -} - - -_patch5 = { - "id": 105, - "bug_id": 77, - "url": "http://example.com/103", - "name": "Patch5", - "is_obsolete": False, - "is_patch": True, - "review": "+", - "reviewer_email": "foo@bar.com", - "attacher_email": "eric@webkit.org", -} - - -_patch6 = { # Valid committer, but no reviewer. - "id": 106, - "bug_id": 77, - "url": "http://example.com/103", - "name": "ROLLOUT of r3489", - "is_obsolete": False, - "is_patch": True, - "commit-queue": "+", - "committer_email": "foo@bar.com", - "attacher_email": "eric@webkit.org", -} - - -_patch7 = { # Valid review, patch is marked obsolete. - "id": 107, - "bug_id": 76, - "url": "http://example.com/103", - "name": "Patch7", - "is_obsolete": True, - "is_patch": True, - "review": "+", - "reviewer_email": "foo@bar.com", - "attacher_email": "eric@webkit.org", -} - - -# This matches one of Bug.unassigned_emails -_unassigned_email = "webkit-unassigned@lists.webkit.org" - - -# FIXME: The ids should be 1, 2, 3 instead of crazy numbers. - - -_bug1 = { - "id": 42, - "title": "Bug with two r+'d and cq+'d patches, one of which has an " - "invalid commit-queue setter.", - "assigned_to_email": _unassigned_email, - "attachments": [_patch1, _patch2], - "bug_status": "UNCONFIRMED", -} - - -_bug2 = { - "id": 75, - "title": "Bug with a patch needing review.", - "assigned_to_email": "foo@foo.com", - "attachments": [_patch3], - "bug_status": "ASSIGNED", -} - - -_bug3 = { - "id": 76, - "title": "The third bug", - "assigned_to_email": _unassigned_email, - "attachments": [_patch7], - "bug_status": "NEW", -} - - -_bug4 = { - "id": 77, - "title": "The fourth bug", - "assigned_to_email": "foo@foo.com", - "attachments": [_patch4, _patch5, _patch6], - "bug_status": "REOPENED", -} - - -# FIXME: This should not inherit from Mock -class MockBugzillaQueries(Mock): - - def __init__(self, bugzilla): - Mock.__init__(self) - self._bugzilla = bugzilla - - def _all_bugs(self): - return map(lambda bug_dictionary: Bug(bug_dictionary, self._bugzilla), - self._bugzilla.bug_cache.values()) - - def fetch_bug_ids_from_commit_queue(self): - bugs_with_commit_queued_patches = filter( - lambda bug: bug.commit_queued_patches(), - self._all_bugs()) - return map(lambda bug: bug.id(), bugs_with_commit_queued_patches) - - def fetch_attachment_ids_from_review_queue(self): - unreviewed_patches = sum([bug.unreviewed_patches() - for bug in self._all_bugs()], []) - return map(lambda patch: patch.id(), unreviewed_patches) - - def fetch_patches_from_commit_queue(self): - return sum([bug.commit_queued_patches() - for bug in self._all_bugs()], []) - - def fetch_bug_ids_from_pending_commit_list(self): - bugs_with_reviewed_patches = filter(lambda bug: bug.reviewed_patches(), - self._all_bugs()) - bug_ids = map(lambda bug: bug.id(), bugs_with_reviewed_patches) - # NOTE: This manual hack here is to allow testing logging in - # test_assign_to_committer the real pending-commit query on bugzilla - # will return bugs with patches which have r+, but are also obsolete. - return bug_ids + [76] - - def fetch_patches_from_pending_commit_list(self): - return sum([bug.reviewed_patches() for bug in self._all_bugs()], []) - - -_mock_reviewer = Reviewer("Foo Bar", "foo@bar.com") - - -# FIXME: Bugzilla is the wrong Mock-point. Once we have a BugzillaNetwork -# class we should mock that instead. -# Most of this class is just copy/paste from Bugzilla. -# FIXME: This should not inherit from Mock -class MockBugzilla(Mock): - - bug_server_url = "http://example.com" - - bug_cache = _id_to_object_dictionary(_bug1, _bug2, _bug3, _bug4) - - attachment_cache = _id_to_object_dictionary(_patch1, - _patch2, - _patch3, - _patch4, - _patch5, - _patch6, - _patch7) - - def __init__(self): - Mock.__init__(self) - self.queries = MockBugzillaQueries(self) - self.committers = CommitterList(reviewers=[_mock_reviewer]) - self._override_patch = None - - def create_bug(self, - bug_title, - bug_description, - component=None, - diff=None, - patch_description=None, - cc=None, - blocked=None, - mark_for_review=False, - mark_for_commit_queue=False): - log("MOCK create_bug") - log("bug_title: %s" % bug_title) - log("bug_description: %s" % bug_description) - - def quips(self): - return ["Good artists copy. Great artists steal. - Pablo Picasso"] - - def fetch_bug(self, bug_id): - return Bug(self.bug_cache.get(bug_id), self) - - def set_override_patch(self, patch): - self._override_patch = patch - - def fetch_attachment(self, attachment_id): - if self._override_patch: - return self._override_patch - - attachment_dictionary = self.attachment_cache.get(attachment_id) - if not attachment_dictionary: - print "MOCK: fetch_attachment: %s is not a known attachment id" % attachment_id - return None - bug = self.fetch_bug(attachment_dictionary["bug_id"]) - for attachment in bug.attachments(include_obsolete=True): - if attachment.id() == int(attachment_id): - return attachment - - def bug_url_for_bug_id(self, bug_id): - return "%s/%s" % (self.bug_server_url, bug_id) - - def fetch_bug_dictionary(self, bug_id): - return self.bug_cache.get(bug_id) - - def attachment_url_for_id(self, attachment_id, action="view"): - action_param = "" - if action and action != "view": - action_param = "&action=%s" % action - return "%s/%s%s" % (self.bug_server_url, attachment_id, action_param) - - def set_flag_on_attachment(self, - attachment_id, - flag_name, - flag_value, - comment_text=None, - additional_comment_text=None): - log("MOCK setting flag '%s' to '%s' on attachment '%s' with comment '%s' and additional comment '%s'" % ( - flag_name, flag_value, attachment_id, comment_text, additional_comment_text)) - - def post_comment_to_bug(self, bug_id, comment_text, cc=None): - log("MOCK bug comment: bug_id=%s, cc=%s\n--- Begin comment ---\n%s\n--- End comment ---\n" % ( - bug_id, cc, comment_text)) - - 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): - log("MOCK add_patch_to_bug: bug_id=%s, description=%s, mark_for_review=%s, mark_for_commit_queue=%s, mark_for_landing=%s" % - (bug_id, description, mark_for_review, mark_for_commit_queue, mark_for_landing)) - log("-- Begin comment --") - log(comment_text) - log("-- End comment --") - - -class MockBuilder(object): - def __init__(self, name): - self._name = name - - def name(self): - return self._name - - def results_url(self): - return "http://example.com/builders/%s/results/" % self.name() - - def force_build(self, username, comments): - log("MOCK: force_build: name=%s, username=%s, comments=%s" % ( - self._name, username, comments)) - - -class MockFailureMap(object): - def __init__(self, buildbot): - self._buildbot = buildbot - - def is_empty(self): - return False - - def filter_out_old_failures(self, is_old_revision): - pass - - def failing_revisions(self): - return [29837] - - def builders_failing_for(self, revision): - return [self._buildbot.builder_with_name("Builder1")] - - def tests_failing_for(self, revision): - return ["mock-test-1"] - - -class MockBuildBot(object): - buildbot_host = "dummy_buildbot_host" - def __init__(self): - self._mock_builder1_status = { - "name": "Builder1", - "is_green": True, - "activity": "building", - } - self._mock_builder2_status = { - "name": "Builder2", - "is_green": True, - "activity": "idle", - } - - def builder_with_name(self, name): - return MockBuilder(name) - - def builder_statuses(self): - return [ - self._mock_builder1_status, - self._mock_builder2_status, - ] - - def red_core_builders_names(self): - if not self._mock_builder2_status["is_green"]: - return [self._mock_builder2_status["name"]] - return [] - - def red_core_builders(self): - if not self._mock_builder2_status["is_green"]: - return [self._mock_builder2_status] - return [] - - def idle_red_core_builders(self): - if not self._mock_builder2_status["is_green"]: - return [self._mock_builder2_status] - return [] - - def last_green_revision(self): - return 9479 - - def light_tree_on_fire(self): - self._mock_builder2_status["is_green"] = False - - def failure_map(self): - return MockFailureMap(self) - - -# FIXME: This should not inherit from Mock -class MockSCM(Mock): - - fake_checkout_root = os.path.realpath("/tmp") # realpath is needed to allow for Mac OS X's /private/tmp - - def __init__(self): - Mock.__init__(self) - # FIXME: We should probably use real checkout-root detection logic here. - # os.getcwd() can't work here because other parts of the code assume that "checkout_root" - # will actually be the root. Since getcwd() is wrong, use a globally fake root for now. - self.checkout_root = self.fake_checkout_root - - def changed_files(self, git_commit=None): - return ["MockFile1"] - - def create_patch(self, git_commit, changed_files=None): - return "Patch1" - - def commit_ids_from_commitish_arguments(self, args): - return ["Commitish1", "Commitish2"] - - def commit_message_for_local_commit(self, commit_id): - if commit_id == "Commitish1": - return CommitMessage("CommitMessage1\n" \ - "https://bugs.example.org/show_bug.cgi?id=42\n") - if commit_id == "Commitish2": - return CommitMessage("CommitMessage2\n" \ - "https://bugs.example.org/show_bug.cgi?id=75\n") - raise Exception("Bogus commit_id in commit_message_for_local_commit.") - - def diff_for_revision(self, revision): - return "DiffForRevision%s\n" \ - "http://bugs.webkit.org/show_bug.cgi?id=12345" % revision - - def svn_revision_from_commit_text(self, commit_text): - return "49824" - - -class MockCheckout(object): - - _committer_list = CommitterList() - - def commit_info_for_revision(self, svn_revision): - # The real Checkout would probably throw an exception, but this is the only way tests have to get None back at the moment. - if not svn_revision: - return None - return CommitInfo(svn_revision, "eric@webkit.org", { - "bug_id": 42, - "author_name": "Adam Barth", - "author_email": "abarth@webkit.org", - "author": self._committer_list.committer_by_email("abarth@webkit.org"), - "reviewer_text": "Darin Adler", - "reviewer": self._committer_list.committer_by_name("Darin Adler"), - }) - - def bug_id_for_revision(self, svn_revision): - return 12345 - - def recent_commit_infos_for_files(self, paths): - return [self.commit_info_for_revision(32)] - - def modified_changelogs(self, git_commit, changed_files=None): - # Ideally we'd return something more interesting here. The problem is - # that LandDiff will try to actually read the patch from disk! - return [] - - def commit_message_for_this_commit(self, git_commit, changed_files=None): - commit_message = Mock() - commit_message.message = lambda:"This is a fake commit message that is at least 50 characters." - return commit_message - - def apply_patch(self, patch, force=False): - pass - - def apply_reverse_diff(self, revision): - pass - - def suggested_reviewers(self, git_commit, changed_files=None): - return [_mock_reviewer] - - -class MockUser(object): - - @staticmethod - def prompt(message, repeat=1, raw_input=raw_input): - return "Mock user response" - - def edit(self, files): - pass - - def edit_changelog(self, files): - pass - - def page(self, message): - pass - - def confirm(self, message=None, default='y'): - print message - return default == 'y' - - def can_open_url(self): - return True - - def open_url(self, url): - if url.startswith("file://"): - log("MOCK: user.open_url: file://...") - return - log("MOCK: user.open_url: %s" % url) - - -class MockIRC(object): - - def post(self, message): - log("MOCK: irc.post: %s" % message) - - def disconnect(self): - log("MOCK: irc.disconnect") - - -class MockStatusServer(object): - - def __init__(self, bot_id=None, work_items=None): - self.host = "example.com" - self.bot_id = bot_id - self._work_items = work_items or [] - - def patch_status(self, queue_name, patch_id): - return None - - def svn_revision(self, svn_revision): - return None - - def next_work_item(self, queue_name): - if not self._work_items: - return None - return self._work_items.pop(0) - - def release_work_item(self, queue_name, patch): - log("MOCK: release_work_item: %s %s" % (queue_name, patch.id())) - - def update_work_items(self, queue_name, work_items): - self._work_items = work_items - log("MOCK: update_work_items: %s %s" % (queue_name, work_items)) - - def submit_to_ews(self, patch_id): - log("MOCK: submit_to_ews: %s" % (patch_id)) - - def update_status(self, queue_name, status, patch=None, results_file=None): - log("MOCK: update_status: %s %s" % (queue_name, status)) - return 187 - - def update_svn_revision(self, svn_revision, broken_bot): - return 191 - - def results_url_for_status(self, status_id): - return "http://dummy_url" - - -# FIXME: This should not inherit from Mock -# FIXME: Unify with common.system.executive_mock.MockExecutive. -class MockExecutive(Mock): - def __init__(self, should_log): - self._should_log = should_log - - def run_and_throw_if_fail(self, args, quiet=False): - if self._should_log: - log("MOCK run_and_throw_if_fail: %s" % args) - return "MOCK output of child process" - - def run_command(self, - args, - cwd=None, - input=None, - error_handler=None, - return_exit_code=False, - return_stderr=True, - decode_output=False): - if self._should_log: - log("MOCK run_command: %s" % args) - return "MOCK output of child process" - - -class MockOptions(object): - """Mock implementation of optparse.Values.""" - - def __init__(self, **kwargs): - # The caller can set option values using keyword arguments. We don't - # set any values by default because we don't know how this - # object will be used. Generally speaking unit tests should - # subclass this or provider wrapper functions that set a common - # set of options. - for key, value in kwargs.items(): - self.__dict__[key] = value - - -class MockTestPort1(object): - - def skips_layout_test(self, test_name): - return test_name in ["media/foo/bar.html", "foo"] - - -class MockTestPort2(object): - - def skips_layout_test(self, test_name): - return test_name == "media/foo/bar.html" - - -class MockPortFactory(object): - - def get_all(self, options=None): - return {"test_port1": MockTestPort1(), "test_port2": MockTestPort2()} - - -class MockTool(object): - - def __init__(self, log_executive=False): - self.wakeup_event = threading.Event() - self.bugs = MockBugzilla() - self.buildbot = MockBuildBot() - self.executive = MockExecutive(should_log=log_executive) - self._irc = None - self.user = MockUser() - self._scm = MockSCM() - self._checkout = MockCheckout() - self.status_server = MockStatusServer() - self.irc_password = "MOCK irc password" - self.port_factory = MockPortFactory() - - def scm(self): - return self._scm - - def checkout(self): - return self._checkout - - def ensure_irc_connected(self, delegate): - if not self._irc: - self._irc = MockIRC() - - def irc(self): - return self._irc - - def path(self): - return "echo" - - def port(self): - return Mock() - - -class MockBrowser(object): - params = {} - - def open(self, url): - pass - - def select_form(self, name): - pass - - def __setitem__(self, key, value): - self.params[key] = value - - def submit(self): - return Mock(file) diff --git a/WebKitTools/Scripts/webkitpy/tool/mocktool_unittest.py b/WebKitTools/Scripts/webkitpy/tool/mocktool_unittest.py deleted file mode 100644 index cceaa2e..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/mocktool_unittest.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. - -import unittest - -from mocktool import MockOptions - - -class MockOptionsTest(unittest.TestCase): - # MockOptions() should implement the same semantics that - # optparse.Values does. - - def test_get__set(self): - # Test that we can still set options after we construct the - # object. - options = MockOptions() - options.foo = 'bar' - self.assertEqual(options.foo, 'bar') - - def test_get__unset(self): - # Test that unset options raise an exception (regular Mock - # objects return an object and hence are different from - # optparse.Values()). - options = MockOptions() - self.assertRaises(AttributeError, lambda: options.foo) - - def test_kwarg__set(self): - # Test that keyword arguments work in the constructor. - options = MockOptions(foo='bar') - self.assertEqual(options.foo, 'bar') - - -if __name__ == '__main__': - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/tool/multicommandtool.py b/WebKitTools/Scripts/webkitpy/tool/multicommandtool.py deleted file mode 100644 index 4848ae5..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/multicommandtool.py +++ /dev/null @@ -1,314 +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. -# -# MultiCommandTool provides a framework for writing svn-like/git-like tools -# which are called with the following format: -# tool-name [global options] command-name [command options] - -import sys - -from optparse import OptionParser, IndentedHelpFormatter, SUPPRESS_USAGE, make_option - -from webkitpy.tool.grammar import pluralize -from webkitpy.common.system.deprecated_logging import log - - -class TryAgain(Exception): - pass - - -class Command(object): - name = None - show_in_main_help = False - def __init__(self, help_text, argument_names=None, options=None, long_help=None, requires_local_commits=False): - self.help_text = help_text - self.long_help = long_help - self.argument_names = argument_names - self.required_arguments = self._parse_required_arguments(argument_names) - self.options = options - self.requires_local_commits = requires_local_commits - self._tool = None - # option_parser can be overriden by the tool using set_option_parser - # This default parser will be used for standalone_help printing. - self.option_parser = HelpPrintingOptionParser(usage=SUPPRESS_USAGE, add_help_option=False, option_list=self.options) - - # This design is slightly awkward, but we need the - # the tool to be able to create and modify the option_parser - # before it knows what Command to run. - def set_option_parser(self, option_parser): - self.option_parser = option_parser - self._add_options_to_parser() - - def _add_options_to_parser(self): - options = self.options or [] - for option in options: - self.option_parser.add_option(option) - - # The tool calls bind_to_tool on each Command after adding it to its list. - def bind_to_tool(self, tool): - # Command instances can only be bound to one tool at a time. - if self._tool and tool != self._tool: - raise Exception("Command already bound to tool!") - self._tool = tool - - @staticmethod - def _parse_required_arguments(argument_names): - required_args = [] - if not argument_names: - return required_args - split_args = argument_names.split(" ") - for argument in split_args: - if argument[0] == '[': - # For now our parser is rather dumb. Do some minimal validation that - # we haven't confused it. - if argument[-1] != ']': - raise Exception("Failure to parse argument string %s. Argument %s is missing ending ]" % (argument_names, argument)) - else: - required_args.append(argument) - return required_args - - def name_with_arguments(self): - usage_string = self.name - if self.options: - usage_string += " [options]" - if self.argument_names: - usage_string += " " + self.argument_names - return usage_string - - def parse_args(self, args): - return self.option_parser.parse_args(args) - - def check_arguments_and_execute(self, options, args, tool=None): - if len(args) < len(self.required_arguments): - log("%s required, %s provided. Provided: %s Required: %s\nSee '%s help %s' for usage." % ( - pluralize("argument", len(self.required_arguments)), - pluralize("argument", len(args)), - "'%s'" % " ".join(args), - " ".join(self.required_arguments), - tool.name(), - self.name)) - return 1 - return self.execute(options, args, tool) or 0 - - def standalone_help(self): - help_text = self.name_with_arguments().ljust(len(self.name_with_arguments()) + 3) + self.help_text + "\n\n" - if self.long_help: - help_text += "%s\n\n" % self.long_help - help_text += self.option_parser.format_option_help(IndentedHelpFormatter()) - return help_text - - def execute(self, options, args, tool): - raise NotImplementedError, "subclasses must implement" - - # main() exists so that Commands can be turned into stand-alone scripts. - # Other parts of the code will likely require modification to work stand-alone. - def main(self, args=sys.argv): - (options, args) = self.parse_args(args) - # Some commands might require a dummy tool - return self.check_arguments_and_execute(options, args) - - -# FIXME: This should just be rolled into Command. help_text and argument_names do not need to be instance variables. -class AbstractDeclarativeCommand(Command): - help_text = None - argument_names = None - long_help = None - def __init__(self, options=None, **kwargs): - Command.__init__(self, self.help_text, self.argument_names, options=options, long_help=self.long_help, **kwargs) - - -class HelpPrintingOptionParser(OptionParser): - def __init__(self, epilog_method=None, *args, **kwargs): - self.epilog_method = epilog_method - OptionParser.__init__(self, *args, **kwargs) - - def error(self, msg): - self.print_usage(sys.stderr) - error_message = "%s: error: %s\n" % (self.get_prog_name(), msg) - # This method is overriden to add this one line to the output: - error_message += "\nType \"%s --help\" to see usage.\n" % self.get_prog_name() - self.exit(1, error_message) - - # We override format_epilog to avoid the default formatting which would paragraph-wrap the epilog - # and also to allow us to compute the epilog lazily instead of in the constructor (allowing it to be context sensitive). - def format_epilog(self, epilog): - if self.epilog_method: - return "\n%s\n" % self.epilog_method() - return "" - - -class HelpCommand(AbstractDeclarativeCommand): - name = "help" - help_text = "Display information about this program or its subcommands" - argument_names = "[COMMAND]" - - def __init__(self): - options = [ - make_option("-a", "--all-commands", action="store_true", dest="show_all_commands", help="Print all available commands"), - ] - AbstractDeclarativeCommand.__init__(self, options) - self.show_all_commands = False # A hack used to pass --all-commands to _help_epilog even though it's called by the OptionParser. - - def _help_epilog(self): - # Only show commands which are relevant to this checkout's SCM system. Might this be confusing to some users? - if self.show_all_commands: - epilog = "All %prog commands:\n" - relevant_commands = self._tool.commands[:] - else: - epilog = "Common %prog commands:\n" - relevant_commands = filter(self._tool.should_show_in_main_help, self._tool.commands) - longest_name_length = max(map(lambda command: len(command.name), relevant_commands)) - relevant_commands.sort(lambda a, b: cmp(a.name, b.name)) - command_help_texts = map(lambda command: " %s %s\n" % (command.name.ljust(longest_name_length), command.help_text), relevant_commands) - epilog += "%s\n" % "".join(command_help_texts) - epilog += "See '%prog help --all-commands' to list all commands.\n" - epilog += "See '%prog help COMMAND' for more information on a specific command.\n" - return epilog.replace("%prog", self._tool.name()) # Use of %prog here mimics OptionParser.expand_prog_name(). - - # FIXME: This is a hack so that we don't show --all-commands as a global option: - def _remove_help_options(self): - for option in self.options: - self.option_parser.remove_option(option.get_opt_string()) - - def execute(self, options, args, tool): - if args: - command = self._tool.command_by_name(args[0]) - if command: - print command.standalone_help() - return 0 - - self.show_all_commands = options.show_all_commands - self._remove_help_options() - self.option_parser.print_help() - return 0 - - -class MultiCommandTool(object): - global_options = None - - def __init__(self, name=None, commands=None): - self._name = name or OptionParser(prog=name).get_prog_name() # OptionParser has nice logic for fetching the name. - # Allow the unit tests to disable command auto-discovery. - self.commands = commands or [cls() for cls in self._find_all_commands() if cls.name] - self.help_command = self.command_by_name(HelpCommand.name) - # Require a help command, even if the manual test list doesn't include one. - if not self.help_command: - self.help_command = HelpCommand() - self.commands.append(self.help_command) - for command in self.commands: - command.bind_to_tool(self) - - @classmethod - def _add_all_subclasses(cls, class_to_crawl, seen_classes): - for subclass in class_to_crawl.__subclasses__(): - if subclass not in seen_classes: - seen_classes.add(subclass) - cls._add_all_subclasses(subclass, seen_classes) - - @classmethod - def _find_all_commands(cls): - commands = set() - cls._add_all_subclasses(Command, commands) - return sorted(commands) - - def name(self): - return self._name - - def _create_option_parser(self): - usage = "Usage: %prog [options] COMMAND [ARGS]" - return HelpPrintingOptionParser(epilog_method=self.help_command._help_epilog, prog=self.name(), usage=usage) - - @staticmethod - def _split_command_name_from_args(args): - # Assume the first argument which doesn't start with "-" is the command name. - command_index = 0 - for arg in args: - if arg[0] != "-": - break - command_index += 1 - else: - return (None, args[:]) - - command = args[command_index] - return (command, args[:command_index] + args[command_index + 1:]) - - def command_by_name(self, command_name): - for command in self.commands: - if command_name == command.name: - return command - return None - - def path(self): - raise NotImplementedError, "subclasses must implement" - - def command_completed(self): - pass - - def should_show_in_main_help(self, command): - return command.show_in_main_help - - def should_execute_command(self, command): - return True - - def _add_global_options(self, option_parser): - global_options = self.global_options or [] - for option in global_options: - option_parser.add_option(option) - - def handle_global_options(self, options): - pass - - def main(self, argv=sys.argv): - (command_name, args) = self._split_command_name_from_args(argv[1:]) - - option_parser = self._create_option_parser() - self._add_global_options(option_parser) - - command = self.command_by_name(command_name) or self.help_command - if not command: - option_parser.error("%s is not a recognized command" % command_name) - - command.set_option_parser(option_parser) - (options, args) = command.parse_args(args) - self.handle_global_options(options) - - (should_execute, failure_reason) = self.should_execute_command(command) - if not should_execute: - log(failure_reason) - return 0 # FIXME: Should this really be 0? - - while True: - try: - result = command.check_arguments_and_execute(options, args, self) - break - except TryAgain, e: - pass - - self.command_completed() - return result diff --git a/WebKitTools/Scripts/webkitpy/tool/multicommandtool_unittest.py b/WebKitTools/Scripts/webkitpy/tool/multicommandtool_unittest.py deleted file mode 100644 index c19095c..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/multicommandtool_unittest.py +++ /dev/null @@ -1,177 +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 sys -import unittest - -from optparse import make_option - -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.tool.multicommandtool import MultiCommandTool, Command, TryAgain - - -class TrivialCommand(Command): - name = "trivial" - show_in_main_help = True - def __init__(self, **kwargs): - Command.__init__(self, "help text", **kwargs) - - def execute(self, options, args, tool): - pass - - -class UncommonCommand(TrivialCommand): - name = "uncommon" - show_in_main_help = False - - -class LikesToRetry(Command): - name = "likes-to-retry" - show_in_main_help = True - - def __init__(self, **kwargs): - Command.__init__(self, "help text", **kwargs) - self.execute_count = 0 - - def execute(self, options, args, tool): - self.execute_count += 1 - if self.execute_count < 2: - raise TryAgain() - - -class CommandTest(unittest.TestCase): - def test_name_with_arguments(self): - command_with_args = TrivialCommand(argument_names="ARG1 ARG2") - self.assertEqual(command_with_args.name_with_arguments(), "trivial ARG1 ARG2") - - command_with_args = TrivialCommand(options=[make_option("--my_option")]) - self.assertEqual(command_with_args.name_with_arguments(), "trivial [options]") - - def test_parse_required_arguments(self): - self.assertEqual(Command._parse_required_arguments("ARG1 ARG2"), ["ARG1", "ARG2"]) - self.assertEqual(Command._parse_required_arguments("[ARG1] [ARG2]"), []) - self.assertEqual(Command._parse_required_arguments("[ARG1] ARG2"), ["ARG2"]) - # Note: We might make our arg parsing smarter in the future and allow this type of arguments string. - self.assertRaises(Exception, Command._parse_required_arguments, "[ARG1 ARG2]") - - def test_required_arguments(self): - two_required_arguments = TrivialCommand(argument_names="ARG1 ARG2 [ARG3]") - expected_missing_args_error = "2 arguments required, 1 argument provided. Provided: 'foo' Required: ARG1 ARG2\nSee 'trivial-tool help trivial' for usage.\n" - exit_code = OutputCapture().assert_outputs(self, two_required_arguments.check_arguments_and_execute, [None, ["foo"], TrivialTool()], expected_stderr=expected_missing_args_error) - self.assertEqual(exit_code, 1) - - -class TrivialTool(MultiCommandTool): - def __init__(self, commands=None): - MultiCommandTool.__init__(self, name="trivial-tool", commands=commands) - - def path(self): - return __file__ - - def should_execute_command(self, command): - return (True, None) - - -class MultiCommandToolTest(unittest.TestCase): - def _assert_split(self, args, expected_split): - self.assertEqual(MultiCommandTool._split_command_name_from_args(args), expected_split) - - def test_split_args(self): - # MultiCommandToolTest._split_command_name_from_args returns: (command, args) - full_args = ["--global-option", "command", "--option", "arg"] - full_args_expected = ("command", ["--global-option", "--option", "arg"]) - self._assert_split(full_args, full_args_expected) - - full_args = [] - full_args_expected = (None, []) - self._assert_split(full_args, full_args_expected) - - full_args = ["command", "arg"] - full_args_expected = ("command", ["arg"]) - self._assert_split(full_args, full_args_expected) - - def test_command_by_name(self): - # This also tests Command auto-discovery. - tool = TrivialTool() - self.assertEqual(tool.command_by_name("trivial").name, "trivial") - self.assertEqual(tool.command_by_name("bar"), None) - - def _assert_tool_main_outputs(self, tool, main_args, expected_stdout, expected_stderr = "", expected_exit_code=0): - exit_code = OutputCapture().assert_outputs(self, tool.main, [main_args], expected_stdout=expected_stdout, expected_stderr=expected_stderr) - self.assertEqual(exit_code, expected_exit_code) - - def test_retry(self): - likes_to_retry = LikesToRetry() - tool = TrivialTool(commands=[likes_to_retry]) - tool.main(["tool", "likes-to-retry"]) - self.assertEqual(likes_to_retry.execute_count, 2) - - def test_global_help(self): - tool = TrivialTool(commands=[TrivialCommand(), UncommonCommand()]) - expected_common_commands_help = """Usage: trivial-tool [options] COMMAND [ARGS] - -Options: - -h, --help show this help message and exit - -Common trivial-tool commands: - trivial help text - -See 'trivial-tool help --all-commands' to list all commands. -See 'trivial-tool help COMMAND' for more information on a specific command. - -""" - self._assert_tool_main_outputs(tool, ["tool"], expected_common_commands_help) - self._assert_tool_main_outputs(tool, ["tool", "help"], expected_common_commands_help) - expected_all_commands_help = """Usage: trivial-tool [options] COMMAND [ARGS] - -Options: - -h, --help show this help message and exit - -All trivial-tool commands: - help Display information about this program or its subcommands - trivial help text - uncommon help text - -See 'trivial-tool help --all-commands' to list all commands. -See 'trivial-tool help COMMAND' for more information on a specific command. - -""" - self._assert_tool_main_outputs(tool, ["tool", "help", "--all-commands"], expected_all_commands_help) - # Test that arguments can be passed before commands as well - self._assert_tool_main_outputs(tool, ["tool", "--all-commands", "help"], expected_all_commands_help) - - - def test_command_help(self): - command_with_options = TrivialCommand(options=[make_option("--my_option")], long_help="LONG HELP") - tool = TrivialTool(commands=[command_with_options]) - expected_subcommand_help = "trivial [options] help text\n\nLONG HELP\n\nOptions:\n --my_option=MY_OPTION\n\n" - self._assert_tool_main_outputs(tool, ["tool", "help", "trivial"], expected_subcommand_help) - - -if __name__ == "__main__": - unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/__init__.py b/WebKitTools/Scripts/webkitpy/tool/steps/__init__.py deleted file mode 100644 index 64d9d05..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/__init__.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. - -# FIXME: Is this the right way to do this? -from webkitpy.tool.steps.applypatch import ApplyPatch -from webkitpy.tool.steps.applypatchwithlocalcommit import ApplyPatchWithLocalCommit -from webkitpy.tool.steps.build import Build -from webkitpy.tool.steps.checkstyle import CheckStyle -from webkitpy.tool.steps.cleanworkingdirectory import CleanWorkingDirectory -from webkitpy.tool.steps.cleanworkingdirectorywithlocalcommits import CleanWorkingDirectoryWithLocalCommits -from webkitpy.tool.steps.closebug import CloseBug -from webkitpy.tool.steps.closebugforlanddiff import CloseBugForLandDiff -from webkitpy.tool.steps.closepatch import ClosePatch -from webkitpy.tool.steps.commit import Commit -from webkitpy.tool.steps.confirmdiff import ConfirmDiff -from webkitpy.tool.steps.createbug import CreateBug -from webkitpy.tool.steps.editchangelog import EditChangeLog -from webkitpy.tool.steps.ensurebuildersaregreen import EnsureBuildersAreGreen -from webkitpy.tool.steps.ensurelocalcommitifneeded import EnsureLocalCommitIfNeeded -from webkitpy.tool.steps.obsoletepatches import ObsoletePatches -from webkitpy.tool.steps.options import Options -from webkitpy.tool.steps.postdiff import PostDiff -from webkitpy.tool.steps.postdiffforcommit import PostDiffForCommit -from webkitpy.tool.steps.postdiffforrevert import PostDiffForRevert -from webkitpy.tool.steps.preparechangelogforrevert import PrepareChangeLogForRevert -from webkitpy.tool.steps.preparechangelog import PrepareChangeLog -from webkitpy.tool.steps.promptforbugortitle import PromptForBugOrTitle -from webkitpy.tool.steps.reopenbugafterrollout import ReopenBugAfterRollout -from webkitpy.tool.steps.revertrevision import RevertRevision -from webkitpy.tool.steps.runtests import RunTests -from webkitpy.tool.steps.suggestreviewers import SuggestReviewers -from webkitpy.tool.steps.updatechangelogswithreviewer import UpdateChangeLogsWithReviewer -from webkitpy.tool.steps.update import Update -from webkitpy.tool.steps.validatereviewer import ValidateReviewer diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/abstractstep.py b/WebKitTools/Scripts/webkitpy/tool/steps/abstractstep.py deleted file mode 100644 index 5525ea0..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/abstractstep.py +++ /dev/null @@ -1,79 +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 webkitpy.common.system.deprecated_logging import log -from webkitpy.common.system.executive import ScriptError -from webkitpy.common.config.ports import WebKitPort -from webkitpy.tool.steps.options import Options - - -class AbstractStep(object): - def __init__(self, tool, options): - self._tool = tool - self._options = options - - # FIXME: This should use tool.port() - def _run_script(self, script_name, args=None, quiet=False, port=WebKitPort): - log("Running %s" % script_name) - command = [port.script_path(script_name)] - if args: - command.extend(args) - self._tool.executive.run_and_throw_if_fail(command, quiet) - - def _changed_files(self, state): - return self.cached_lookup(state, "changed_files") - - _well_known_keys = { - "bug_title": lambda self, state: self._tool.bugs.fetch_bug(state["bug_id"]).title(), - "changed_files": lambda self, state: self._tool.scm().changed_files(self._options.git_commit), - "diff": lambda self, state: self._tool.scm().create_patch(self._options.git_commit, changed_files=self._changed_files(state)), - "changelogs": lambda self, state: self._tool.checkout().modified_changelogs(self._options.git_commit, changed_files=self._changed_files(state)), - } - - def cached_lookup(self, state, key, promise=None): - if state.get(key): - return state[key] - if not promise: - promise = self._well_known_keys.get(key) - state[key] = promise(self, state) - return state[key] - - def did_modify_checkout(self, state): - state["diff"] = None - state["changelogs"] = None - state["changed_files"] = None - - @classmethod - def options(cls): - return [ - # We need this option here because cached_lookup uses it. :( - Options.git_commit, - ] - - def run(self, state): - raise NotImplementedError, "subclasses must implement" diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/applypatch.py b/WebKitTools/Scripts/webkitpy/tool/steps/applypatch.py deleted file mode 100644 index 327ac09..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/applypatch.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. - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import log - -class ApplyPatch(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.non_interactive, - Options.force_patch, - ] - - def run(self, state): - log("Processing patch %s from bug %s." % (state["patch"].id(), state["patch"].bug_id())) - self._tool.checkout().apply_patch(state["patch"], force=self._options.non_interactive or self._options.force_patch) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/applypatchwithlocalcommit.py b/WebKitTools/Scripts/webkitpy/tool/steps/applypatchwithlocalcommit.py deleted file mode 100644 index 3dcd8d9..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/applypatchwithlocalcommit.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. - -from webkitpy.tool.steps.applypatch import ApplyPatch -from webkitpy.tool.steps.options import Options - -class ApplyPatchWithLocalCommit(ApplyPatch): - @classmethod - def options(cls): - return ApplyPatch.options() + [ - Options.local_commit, - ] - - def run(self, state): - ApplyPatch.run(self, state) - if self._options.local_commit: - commit_message = self._tool.checkout().commit_message_for_this_commit(git_commit=None) - self._tool.scm().commit_locally_with_message(commit_message.message() or state["patch"].name()) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/build.py b/WebKitTools/Scripts/webkitpy/tool/steps/build.py deleted file mode 100644 index 0990b8b..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/build.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 webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import log - - -class Build(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.build, - Options.quiet, - Options.build_style, - ] - - def build(self, build_style): - self._tool.executive.run_and_throw_if_fail(self._tool.port().build_webkit_command(build_style=build_style), self._options.quiet) - - def run(self, state): - if not self._options.build: - return - log("Building WebKit") - if self._options.build_style == "both": - self.build("debug") - self.build("release") - else: - self.build(self._options.build_style) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py b/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.py deleted file mode 100644 index af66c50..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/checkstyle.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 - -from webkitpy.common.system.executive import ScriptError -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import error - -class CheckStyle(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.non_interactive, - Options.check_style, - Options.git_commit, - ] - - def run(self, state): - if not self._options.check_style: - return - os.chdir(self._tool.scm().checkout_root) - - args = [] - if self._options.git_commit: - args.append("--git-commit") - args.append(self._options.git_commit) - - args.append("--diff-files") - args.extend(self._changed_files(state)) - - try: - self._run_script("check-webkit-style", args) - except ScriptError, e: - if self._options.non_interactive: - # We need to re-raise the exception here to have the - # style-queue do the right thing. - raise e - if not self._tool.user.confirm("Are you sure you want to continue?"): - exit(1) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py b/WebKitTools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py deleted file mode 100644 index e13fbc2..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py +++ /dev/null @@ -1,52 +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 - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - - -class CleanWorkingDirectory(AbstractStep): - def __init__(self, tool, options, allow_local_commits=False): - AbstractStep.__init__(self, tool, options) - self._allow_local_commits = allow_local_commits - - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.force_clean, - Options.clean, - ] - - def run(self, state): - os.chdir(self._tool.scm().checkout_root) - if not self._allow_local_commits: - self._tool.scm().ensure_no_local_commits(self._options.force_clean) - if self._options.clean: - self._tool.scm().ensure_clean_working_directory(force_clean=self._options.force_clean) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/cleanworkingdirectorywithlocalcommits.py b/WebKitTools/Scripts/webkitpy/tool/steps/cleanworkingdirectorywithlocalcommits.py deleted file mode 100644 index f06f94e..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/cleanworkingdirectorywithlocalcommits.py +++ /dev/null @@ -1,34 +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 webkitpy.tool.steps.cleanworkingdirectory import CleanWorkingDirectory - -class CleanWorkingDirectoryWithLocalCommits(CleanWorkingDirectory): - def __init__(self, tool, options): - # FIXME: This a bit of a hack. Consider doing this more cleanly. - CleanWorkingDirectory.__init__(self, tool, options, allow_local_commits=True) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/closebug.py b/WebKitTools/Scripts/webkitpy/tool/steps/closebug.py deleted file mode 100644 index e77bc24..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/closebug.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. - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import log - - -class CloseBug(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.close_bug, - ] - - def run(self, state): - if not self._options.close_bug: - return - # Check to make sure there are no r? or r+ patches on the bug before closing. - # Assume that r- patches are just previous patches someone forgot to obsolete. - patches = self._tool.bugs.fetch_bug(state["patch"].bug_id()).patches() - for patch in patches: - if patch.review() == "?" or patch.review() == "+": - log("Not closing bug %s as attachment %s has review=%s. Assuming there are more patches to land from this bug." % (patch.bug_id(), patch.id(), patch.review())) - return - self._tool.bugs.close_bug_as_fixed(state["patch"].bug_id(), "All reviewed patches have been landed. Closing bug.") diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/closebugforlanddiff.py b/WebKitTools/Scripts/webkitpy/tool/steps/closebugforlanddiff.py deleted file mode 100644 index e5a68db..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/closebugforlanddiff.py +++ /dev/null @@ -1,58 +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 webkitpy.tool.comments import bug_comment_from_commit_text -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import log - - -class CloseBugForLandDiff(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.close_bug, - ] - - def run(self, state): - comment_text = bug_comment_from_commit_text(self._tool.scm(), state["commit_text"]) - bug_id = state.get("bug_id") - if not bug_id and state.get("patch"): - bug_id = state.get("patch").bug_id() - - if bug_id: - log("Updating bug %s" % bug_id) - if self._options.close_bug: - self._tool.bugs.close_bug_as_fixed(bug_id, comment_text) - else: - # FIXME: We should a smart way to figure out if the patch is attached - # to the bug, and if so obsolete it. - self._tool.bugs.post_comment_to_bug(bug_id, comment_text) - else: - log(comment_text) - log("No bug id provided.") diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/closebugforlanddiff_unittest.py b/WebKitTools/Scripts/webkitpy/tool/steps/closebugforlanddiff_unittest.py deleted file mode 100644 index 0a56564..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/closebugforlanddiff_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 webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.tool.steps.closebugforlanddiff import CloseBugForLandDiff - -class CloseBugForLandDiffTest(unittest.TestCase): - def test_empty_state(self): - capture = OutputCapture() - step = CloseBugForLandDiff(MockTool(), MockOptions()) - expected_stderr = "Committed r49824: <http://trac.webkit.org/changeset/49824>\nNo bug id provided.\n" - capture.assert_outputs(self, step.run, [{"commit_text" : "Mock commit text"}], expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/closepatch.py b/WebKitTools/Scripts/webkitpy/tool/steps/closepatch.py deleted file mode 100644 index ff94df8..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/closepatch.py +++ /dev/null @@ -1,36 +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 webkitpy.tool.comments import bug_comment_from_commit_text -from webkitpy.tool.steps.abstractstep import AbstractStep - - -class ClosePatch(AbstractStep): - def run(self, state): - comment_text = bug_comment_from_commit_text(self._tool.scm(), state["commit_text"]) - self._tool.bugs.clear_attachment_flags(state["patch"].id(), comment_text) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/commit.py b/WebKitTools/Scripts/webkitpy/tool/steps/commit.py deleted file mode 100644 index 5c6bdb7..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/commit.py +++ /dev/null @@ -1,81 +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 webkitpy.common.checkout.changelog import view_source_url -from webkitpy.common.checkout.scm import AuthenticationError, AmbiguousCommitError -from webkitpy.common.system.deprecated_logging import log -from webkitpy.common.system.executive import ScriptError -from webkitpy.common.system.user import User -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - - -class Commit(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.git_commit, - ] - - def _commit_warning(self, error): - working_directory_message = "" if error.working_directory_is_clean else " and working copy changes" - return ('There are %s local commits%s. Everything will be committed as a single commit. ' - 'To avoid this prompt, set "git config webkit-patch.commit-should-always-squash true".' % ( - error.num_local_commits, working_directory_message)) - - def run(self, state): - self._commit_message = self._tool.checkout().commit_message_for_this_commit(self._options.git_commit).message() - if len(self._commit_message) < 50: - raise Exception("Attempted to commit with a commit message shorter than 50 characters. Either your patch is missing a ChangeLog or webkit-patch may have a bug.") - - self._state = state - - username = None - force_squash = False - - num_tries = 0 - while num_tries < 3: - num_tries += 1 - - try: - scm = self._tool.scm() - commit_text = scm.commit_with_message(self._commit_message, git_commit=self._options.git_commit, username=username, force_squash=force_squash) - svn_revision = scm.svn_revision_from_commit_text(commit_text) - log("Committed r%s: <%s>" % (svn_revision, view_source_url(svn_revision))) - self._state["commit_text"] = commit_text - break; - except AmbiguousCommitError, e: - if self._tool.user.confirm(self._commit_warning(e)): - force_squash = True - else: - # This will correctly interrupt the rest of the commit process. - raise ScriptError(message="Did not commit") - except AuthenticationError, e: - username = self._tool.user.prompt("%s login: " % e.server_host, repeat=5) - if not username: - raise ScriptError("You need to specify the username on %s to perform the commit as." % self.svn_server_host) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/confirmdiff.py b/WebKitTools/Scripts/webkitpy/tool/steps/confirmdiff.py deleted file mode 100644 index 7e8e348..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/confirmdiff.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 urllib - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.prettypatch import PrettyPatch -from webkitpy.common.system import logutils -from webkitpy.common.system.executive import ScriptError - - -_log = logutils.get_logger(__file__) - - -class ConfirmDiff(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.confirm, - ] - - def _show_pretty_diff(self, diff): - if not self._tool.user.can_open_url(): - return None - - try: - pretty_patch = PrettyPatch(self._tool.executive, - self._tool.scm().checkout_root) - pretty_diff_file = pretty_patch.pretty_diff_file(diff) - url = "file://%s" % urllib.quote(pretty_diff_file.name) - self._tool.user.open_url(url) - # We return the pretty_diff_file here because we need to keep the - # file alive until the user has had a chance to confirm the diff. - return pretty_diff_file - except ScriptError, e: - _log.warning("PrettyPatch failed. :(") - except OSError, e: - _log.warning("PrettyPatch unavailable.") - - def run(self, state): - if not self._options.confirm: - return - diff = self.cached_lookup(state, "diff") - pretty_diff_file = self._show_pretty_diff(diff) - if not pretty_diff_file: - self._tool.user.page(diff) - diff_correct = self._tool.user.confirm("Was that diff correct?") - if pretty_diff_file: - pretty_diff_file.close() - if not diff_correct: - exit(1) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/createbug.py b/WebKitTools/Scripts/webkitpy/tool/steps/createbug.py deleted file mode 100644 index 0ab6f68..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/createbug.py +++ /dev/null @@ -1,52 +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 webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - - -class CreateBug(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.cc, - Options.component, - Options.blocks, - ] - - def run(self, state): - # No need to create a bug if we already have one. - if state.get("bug_id"): - return - cc = self._options.cc - if not cc: - cc = state.get("bug_cc") - blocks = self._options.blocks - if not blocks: - blocks = state.get("bug_blocked") - state["bug_id"] = self._tool.bugs.create_bug(state["bug_title"], state["bug_description"], blocked=blocks, component=self._options.component, cc=cc) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/editchangelog.py b/WebKitTools/Scripts/webkitpy/tool/steps/editchangelog.py deleted file mode 100644 index 4d9646f..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/editchangelog.py +++ /dev/null @@ -1,38 +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 - -from webkitpy.tool.steps.abstractstep import AbstractStep - - -class EditChangeLog(AbstractStep): - def run(self, state): - os.chdir(self._tool.scm().checkout_root) - self._tool.user.edit_changelog(self.cached_lookup(state, "changelogs")) - self.did_modify_checkout(state) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/ensurebuildersaregreen.py b/WebKitTools/Scripts/webkitpy/tool/steps/ensurebuildersaregreen.py deleted file mode 100644 index a4fc174..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/ensurebuildersaregreen.py +++ /dev/null @@ -1,48 +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 webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import log, error - - -class EnsureBuildersAreGreen(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.check_builders, - ] - - def run(self, state): - if not self._options.check_builders: - return - red_builders_names = self._tool.buildbot.red_core_builders_names() - if not red_builders_names: - return - red_builders_names = map(lambda name: "\"%s\"" % name, red_builders_names) # Add quotes around the names. - log("\nWARNING: Builders [%s] are red, please watch your commit carefully.\nSee http://%s/console?category=core\n" % (", ".join(red_builders_names), self._tool.buildbot.buildbot_host)) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/ensurelocalcommitifneeded.py b/WebKitTools/Scripts/webkitpy/tool/steps/ensurelocalcommitifneeded.py deleted file mode 100644 index d0cda46..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/ensurelocalcommitifneeded.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. - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import error - - -class EnsureLocalCommitIfNeeded(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.local_commit, - ] - - def run(self, state): - if self._options.local_commit and not self._tool.scm().supports_local_commits(): - error("--local-commit passed, but %s does not support local commits" % self._tool.scm.display_name()) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/metastep.py b/WebKitTools/Scripts/webkitpy/tool/steps/metastep.py deleted file mode 100644 index 7cbd1c5..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/metastep.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 webkitpy.tool.steps.abstractstep import AbstractStep - - -# FIXME: Unify with StepSequence? I'm not sure yet which is the better design. -class MetaStep(AbstractStep): - substeps = [] # Override in subclasses - def __init__(self, tool, options): - AbstractStep.__init__(self, tool, options) - self._step_instances = [] - for step_class in self.substeps: - self._step_instances.append(step_class(tool, options)) - - @staticmethod - def _collect_options_from_steps(steps): - collected_options = [] - for step in steps: - collected_options = collected_options + step.options() - return collected_options - - @classmethod - def options(cls): - return cls._collect_options_from_steps(cls.substeps) - - def run(self, state): - for step in self._step_instances: - step.run(state) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/obsoletepatches.py b/WebKitTools/Scripts/webkitpy/tool/steps/obsoletepatches.py deleted file mode 100644 index de508c6..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/obsoletepatches.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. - -from webkitpy.tool.grammar import pluralize -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import log - - -class ObsoletePatches(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.obsolete_patches, - ] - - def run(self, state): - if not self._options.obsolete_patches: - return - bug_id = state["bug_id"] - patches = self._tool.bugs.fetch_bug(bug_id).patches() - if not patches: - return - log("Obsoleting %s on bug %s" % (pluralize("old patch", len(patches)), bug_id)) - for patch in patches: - self._tool.bugs.obsolete_attachment(patch.id()) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/options.py b/WebKitTools/Scripts/webkitpy/tool/steps/options.py deleted file mode 100644 index 4f17dd3..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/options.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. - -from optparse import make_option - -class Options(object): - blocks = make_option("--blocks", action="store", dest="blocks", default=None, help="Bug number which the created bug blocks.") - build = make_option("--build", action="store_true", dest="build", default=False, help="Build and run run-webkit-tests before committing.") - build_style = make_option("--build-style", action="store", dest="build_style", default=None, help="Whether to build debug, release, or both.") - cc = make_option("--cc", action="store", type="string", dest="cc", help="Comma-separated list of email addresses to carbon-copy.") - check_builders = make_option("--ignore-builders", action="store_false", dest="check_builders", default=True, help="Don't check to see if the build.webkit.org builders are green before landing.") - check_style = make_option("--ignore-style", action="store_false", dest="check_style", default=True, help="Don't check to see if the patch has proper style before uploading.") - clean = make_option("--no-clean", action="store_false", dest="clean", default=True, help="Don't check if the working directory is clean before applying patches") - close_bug = make_option("--no-close", action="store_false", dest="close_bug", default=True, help="Leave bug open after landing.") - comment = make_option("--comment", action="store", type="string", dest="comment", help="Comment to post to bug.") - component = make_option("--component", action="store", type="string", dest="component", help="Component for the new bug.") - confirm = make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Skip confirmation steps.") - description = make_option("-m", "--description", action="store", type="string", dest="description", help="Description string for the attachment (default: \"patch\")") - email = make_option("--email", action="store", type="string", dest="email", help="Email address to use in ChangeLogs.") - force_clean = make_option("--force-clean", action="store_true", dest="force_clean", default=False, help="Clean working directory before applying patches (removes local changes and commits)") - force_patch = make_option("--force-patch", action="store_true", dest="force_patch", default=False, help="Forcefully applies the patch, continuing past errors.") - git_commit = make_option("-g", "--git-commit", action="store", dest="git_commit", help="Operate on a local commit. If a range, the commits are squashed into one. HEAD.. operates on working copy changes only.") - local_commit = make_option("--local-commit", action="store_true", dest="local_commit", default=False, help="Make a local commit for each applied patch") - non_interactive = make_option("--non-interactive", action="store_true", dest="non_interactive", default=False, help="Never prompt the user, fail as fast as possible.") - obsolete_patches = make_option("--no-obsolete", action="store_false", dest="obsolete_patches", default=True, help="Do not obsolete old patches before posting this one.") - open_bug = make_option("--open-bug", action="store_true", dest="open_bug", default=False, help="Opens the associated bug in a browser.") - parent_command = make_option("--parent-command", action="store", dest="parent_command", default=None, help="(Internal) The command that spawned this instance.") - quiet = make_option("--quiet", action="store_true", dest="quiet", default=False, help="Produce less console output.") - request_commit = make_option("--request-commit", action="store_true", dest="request_commit", default=False, help="Mark the patch as needing auto-commit after review.") - review = make_option("--no-review", action="store_false", dest="review", default=True, help="Do not mark the patch for review.") - reviewer = make_option("-r", "--reviewer", action="store", type="string", dest="reviewer", help="Update ChangeLogs to say Reviewed by REVIEWER.") - suggest_reviewers = make_option("--suggest-reviewers", action="store_true", default=False, help="Offer to CC appropriate reviewers.") - test = make_option("--test", action="store_true", dest="test", default=False, help="Run run-webkit-tests before committing.") - update = make_option("--no-update", action="store_false", dest="update", default=True, help="Don't update the working directory.") diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/postdiff.py b/WebKitTools/Scripts/webkitpy/tool/steps/postdiff.py deleted file mode 100644 index c40b6ff..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/postdiff.py +++ /dev/null @@ -1,50 +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 webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - - -class PostDiff(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.description, - Options.comment, - Options.review, - Options.request_commit, - Options.open_bug, - ] - - def run(self, state): - diff = self.cached_lookup(state, "diff") - description = self._options.description or "Patch" - comment_text = self._options.comment - self._tool.bugs.add_patch_to_bug(state["bug_id"], diff, description, comment_text=comment_text, mark_for_review=self._options.review, mark_for_commit_queue=self._options.request_commit) - if self._options.open_bug: - self._tool.user.open_url(self._tool.bugs.bug_url_for_bug_id(state["bug_id"])) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/postdiffforcommit.py b/WebKitTools/Scripts/webkitpy/tool/steps/postdiffforcommit.py deleted file mode 100644 index 13bc00c..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/postdiffforcommit.py +++ /dev/null @@ -1,39 +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 webkitpy.tool.steps.abstractstep import AbstractStep - - -class PostDiffForCommit(AbstractStep): - def run(self, state): - self._tool.bugs.add_patch_to_bug( - state["bug_id"], - self.cached_lookup(state, "diff"), - "Patch for landing", - mark_for_review=False, - mark_for_landing=True) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/postdiffforrevert.py b/WebKitTools/Scripts/webkitpy/tool/steps/postdiffforrevert.py deleted file mode 100644 index bfa631f..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/postdiffforrevert.py +++ /dev/null @@ -1,49 +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 webkitpy.common.net.bugzilla import Attachment -from webkitpy.tool.steps.abstractstep import AbstractStep - - -class PostDiffForRevert(AbstractStep): - def run(self, state): - comment_text = "Any committer can land this patch automatically by \ -marking it commit-queue+. The commit-queue will build and test \ -the patch before landing to ensure that the rollout will be \ -successful. This process takes approximately 15 minutes.\n\n\ -If you would like to land the rollout faster, you can use the \ -following command:\n\n\ - webkit-patch land-attachment ATTACHMENT_ID --ignore-builders\n\n\ -where ATTACHMENT_ID is the ID of this attachment." - self._tool.bugs.add_patch_to_bug( - state["bug_id"], - self.cached_lookup(state, "diff"), - "%s%s" % (Attachment.rollout_preamble, state["revision"]), - comment_text=comment_text, - mark_for_review=False, - mark_for_commit_queue=True) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/preparechangelog.py b/WebKitTools/Scripts/webkitpy/tool/steps/preparechangelog.py deleted file mode 100644 index 099dfe3..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/preparechangelog.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 os - -from webkitpy.common.checkout.changelog import ChangeLog -from webkitpy.common.system.executive import ScriptError -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import error - - -class PrepareChangeLog(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.quiet, - Options.email, - Options.git_commit, - ] - - def _ensure_bug_url(self, state): - if not state.get("bug_id"): - return - bug_id = state.get("bug_id") - changelogs = self.cached_lookup(state, "changelogs") - for changelog_path in changelogs: - changelog = ChangeLog(changelog_path) - if not changelog.latest_entry().bug_id(): - changelog.set_short_description_and_bug_url( - self.cached_lookup(state, "bug_title"), - self._tool.bugs.bug_url_for_bug_id(bug_id)) - - def run(self, state): - if self.cached_lookup(state, "changelogs"): - self._ensure_bug_url(state) - return - os.chdir(self._tool.scm().checkout_root) - args = [self._tool.port().script_path("prepare-ChangeLog")] - if state.get("bug_id"): - args.append("--bug=%s" % state["bug_id"]) - if self._options.email: - args.append("--email=%s" % self._options.email) - - if self._tool.scm().supports_local_commits(): - args.append("--merge-base=%s" % self._tool.scm().merge_base(self._options.git_commit)) - - try: - self._tool.executive.run_and_throw_if_fail(args, self._options.quiet) - except ScriptError, e: - error("Unable to prepare ChangeLogs.") - self.did_modify_checkout(state) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py b/WebKitTools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py deleted file mode 100644 index eceffdf..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.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. - -import os -import unittest - -from webkitpy.common.checkout.changelog_unittest import ChangeLogTest -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.tool.steps.preparechangelog import PrepareChangeLog - - -class PrepareChangeLogTest(ChangeLogTest): - def test_ensure_bug_url(self): - capture = OutputCapture() - step = PrepareChangeLog(MockTool(), MockOptions()) - 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")) - state = { - "bug_title": "Example title", - "bug_id": 1234, - "changelogs": [changelog_path], - } - capture.assert_outputs(self, step.run, [state]) - actual_contents = self._read_file_contents(changelog_path, "utf-8") - expected_message = "Example title\n http://example.com/1234" - 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) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py b/WebKitTools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py deleted file mode 100644 index 0e78bc2..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/preparechangelogforrevert.py +++ /dev/null @@ -1,44 +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 - -from webkitpy.common.checkout.changelog import ChangeLog -from webkitpy.tool.steps.abstractstep import AbstractStep - - -class PrepareChangeLogForRevert(AbstractStep): - def run(self, state): - # This could move to prepare-ChangeLog by adding a --revert= option. - self._run_script("prepare-ChangeLog") - changelog_paths = self._tool.checkout().modified_changelogs(git_commit=None) - bug_url = self._tool.bugs.bug_url_for_bug_id(state["bug_id"]) if state["bug_id"] else None - for changelog_path in changelog_paths: - # FIXME: Seems we should prepare the message outside of changelogs.py and then just pass in - # text that we want to use to replace the reviewed by line. - ChangeLog(changelog_path).update_for_revert(state["revision"], state["reason"], bug_url) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/promptforbugortitle.py b/WebKitTools/Scripts/webkitpy/tool/steps/promptforbugortitle.py deleted file mode 100644 index 31c913c..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/promptforbugortitle.py +++ /dev/null @@ -1,45 +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 webkitpy.tool.steps.abstractstep import AbstractStep - - -class PromptForBugOrTitle(AbstractStep): - def run(self, state): - # No need to prompt if we alrady have the bug_id. - if state.get("bug_id"): - return - user_response = self._tool.user.prompt("Please enter a bug number or a title for a new bug:\n") - # If the user responds with a number, we assume it's bug number. - # Otherwise we assume it's a bug subject. - try: - state["bug_id"] = int(user_response) - except ValueError, TypeError: - state["bug_title"] = user_response - # FIXME: This is kind of a lame description. - state["bug_description"] = user_response diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/reopenbugafterrollout.py b/WebKitTools/Scripts/webkitpy/tool/steps/reopenbugafterrollout.py deleted file mode 100644 index f369ca9..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/reopenbugafterrollout.py +++ /dev/null @@ -1,44 +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 webkitpy.tool.comments import bug_comment_from_commit_text -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.common.system.deprecated_logging import log - - -class ReopenBugAfterRollout(AbstractStep): - def run(self, state): - commit_comment = bug_comment_from_commit_text(self._tool.scm(), state["commit_text"]) - comment_text = "Reverted r%s for reason:\n\n%s\n\n%s" % (state["revision"], state["reason"], commit_comment) - - bug_id = state["bug_id"] - if not bug_id: - log(comment_text) - log("No bugs were updated.") - return - self._tool.bugs.reopen_bug(bug_id, comment_text) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/revertrevision.py b/WebKitTools/Scripts/webkitpy/tool/steps/revertrevision.py deleted file mode 100644 index bbb794c..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/revertrevision.py +++ /dev/null @@ -1,35 +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 webkitpy.tool.steps.abstractstep import AbstractStep - - -class RevertRevision(AbstractStep): - def run(self, state): - self._tool.checkout().apply_reverse_diff(state["revision"]) - self.did_modify_checkout(state) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py b/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py deleted file mode 100644 index 282e381..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/runtests.py +++ /dev/null @@ -1,81 +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 webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import log - -class RunTests(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.test, - Options.non_interactive, - Options.quiet, - ] - - def run(self, state): - if not self._options.test: - return - - # Run the scripting unit tests first because they're quickest. - log("Running Python unit tests") - self._tool.executive.run_and_throw_if_fail(self._tool.port().run_python_unittests_command()) - log("Running Perl unit tests") - self._tool.executive.run_and_throw_if_fail(self._tool.port().run_perl_unittests_command()) - - javascriptcore_tests_command = self._tool.port().run_javascriptcore_tests_command() - if javascriptcore_tests_command: - log("Running JavaScriptCore tests") - self._tool.executive.run_and_throw_if_fail(javascriptcore_tests_command, quiet=True) - - log("Running run-webkit-tests") - args = self._tool.port().run_webkit_tests_command() - if self._options.non_interactive: - args.append("--no-new-test-results") - args.append("--no-launch-safari") - args.append("--exit-after-n-failures=1") - args.append("--wait-for-httpd") - # FIXME: Hack to work around https://bugs.webkit.org/show_bug.cgi?id=38912 - # when running the commit-queue on a mac leopard machine since compositing - # does not work reliably on Leopard due to various graphics driver/system bugs. - if self._tool.port().name() == "Mac" and self._tool.port().is_leopard(): - tests_to_ignore = [] - tests_to_ignore.append("compositing") - - # media tests are also broken on mac leopard due to - # a separate CoreVideo bug which causes random crashes/hangs - # https://bugs.webkit.org/show_bug.cgi?id=38912 - tests_to_ignore.append("media") - - args.extend(["--ignore-tests", ",".join(tests_to_ignore)]) - - if self._options.quiet: - args.append("--quiet") - self._tool.executive.run_and_throw_if_fail(args) - diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py b/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py deleted file mode 100644 index eabb656..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/steps_unittest.py +++ /dev/null @@ -1,92 +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.system.outputcapture import OutputCapture -from webkitpy.common.config.ports import WebKitPort -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.tool.steps.update import Update -from webkitpy.tool.steps.runtests import RunTests -from webkitpy.tool.steps.promptforbugortitle import PromptForBugOrTitle - - -class StepsTest(unittest.TestCase): - def _step_options(self): - options = MockOptions() - options.non_interactive = True - options.port = 'MOCK port' - options.quiet = True - options.test = True - return options - - def _run_step(self, step, tool=None, options=None, state=None): - if not tool: - tool = MockTool() - if not options: - options = self._step_options() - if not state: - state = {} - step(tool, options).run(state) - - def test_update_step(self): - tool = MockTool() - options = self._step_options() - options.update = True - expected_stderr = "Updating working directory\n" - OutputCapture().assert_outputs(self, self._run_step, [Update, tool, options], expected_stderr=expected_stderr) - - def test_prompt_for_bug_or_title_step(self): - tool = MockTool() - tool.user.prompt = lambda message: 42 - self._run_step(PromptForBugOrTitle, tool=tool) - - def test_runtests_leopard_commit_queue_hack_step(self): - expected_stderr = "Running Python unit tests\nRunning Perl unit tests\nRunning JavaScriptCore tests\nRunning run-webkit-tests\n" - OutputCapture().assert_outputs(self, self._run_step, [RunTests], expected_stderr=expected_stderr) - - def test_runtests_leopard_commit_queue_hack_command(self): - mock_options = self._step_options() - step = RunTests(MockTool(log_executive=True), mock_options) - # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment. - mock_port = WebKitPort() - mock_port.name = lambda: "Mac" - mock_port.is_leopard = lambda: True - tool = MockTool(log_executive=True) - tool.port = lambda: mock_port - step = RunTests(tool, mock_options) - expected_stderr = """Running Python unit tests -MOCK run_and_throw_if_fail: ['WebKitTools/Scripts/test-webkitpy'] -Running Perl unit tests -MOCK run_and_throw_if_fail: ['WebKitTools/Scripts/test-webkitperl'] -Running JavaScriptCore tests -MOCK run_and_throw_if_fail: ['WebKitTools/Scripts/run-javascriptcore-tests'] -Running run-webkit-tests -MOCK run_and_throw_if_fail: ['WebKitTools/Scripts/run-webkit-tests', '--no-new-test-results', '--no-launch-safari', '--exit-after-n-failures=1', '--wait-for-httpd', '--ignore-tests', 'compositing,media', '--quiet'] -""" - OutputCapture().assert_outputs(self, step.run, [{}], expected_stderr=expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/suggestreviewers.py b/WebKitTools/Scripts/webkitpy/tool/steps/suggestreviewers.py deleted file mode 100644 index 76bef35..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/suggestreviewers.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. - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - - -class SuggestReviewers(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.git_commit, - Options.suggest_reviewers, - ] - - def run(self, state): - if not self._options.suggest_reviewers: - return - - reviewers = self._tool.checkout().suggested_reviewers(self._options.git_commit, self._changed_files(state)) - print "The following reviewers have recently modified files in your patch:" - print "\n".join([reviewer.full_name for reviewer in reviewers]) - if not self._tool.user.confirm("Would you like to CC them?"): - return - reviewer_emails = [reviewer.bugzilla_email() for reviewer in reviewers] - self._tool.bugs.add_cc_to_bug(state['bug_id'], reviewer_emails) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py b/WebKitTools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py deleted file mode 100644 index 0c86535..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/suggestreviewers_unittest.py +++ /dev/null @@ -1,45 +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.system.outputcapture import OutputCapture -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.tool.steps.suggestreviewers import SuggestReviewers - - -class SuggestReviewersTest(unittest.TestCase): - def test_disabled(self): - step = SuggestReviewers(MockTool(), MockOptions(suggest_reviewers=False)) - OutputCapture().assert_outputs(self, step.run, [{}]) - - def test_basic(self): - capture = OutputCapture() - step = SuggestReviewers(MockTool(), MockOptions(suggest_reviewers=True, git_commit=None)) - expected_stdout = "The following reviewers have recently modified files in your patch:\nFoo Bar\nWould you like to CC them?\n" - capture.assert_outputs(self, step.run, [{"bug_id": "123"}], expected_stdout=expected_stdout) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/update.py b/WebKitTools/Scripts/webkitpy/tool/steps/update.py deleted file mode 100644 index cd1d4d8..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/update.py +++ /dev/null @@ -1,45 +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 webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import log - - -class Update(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.update, - ] - - def run(self, state): - if not self._options.update: - return - log("Updating working directory") - self._tool.executive.run_and_throw_if_fail(self._tool.port().update_webkit_command(), quiet=True) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/updatechangelogswithreview_unittest.py b/WebKitTools/Scripts/webkitpy/tool/steps/updatechangelogswithreview_unittest.py deleted file mode 100644 index b475378..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/updatechangelogswithreview_unittest.py +++ /dev/null @@ -1,48 +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.system.outputcapture import OutputCapture -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.tool.steps.updatechangelogswithreviewer import UpdateChangeLogsWithReviewer - -class UpdateChangeLogsWithReviewerTest(unittest.TestCase): - def test_guess_reviewer_from_bug(self): - capture = OutputCapture() - step = UpdateChangeLogsWithReviewer(MockTool(), MockOptions()) - expected_stderr = "0 reviewed patches on bug 75, cannot infer reviewer.\n" - capture.assert_outputs(self, step._guess_reviewer_from_bug, [75], expected_stderr=expected_stderr) - - def test_empty_state(self): - capture = OutputCapture() - options = MockOptions() - options.reviewer = 'MOCK reviewer' - options.git_commit = 'MOCK git commit' - step = UpdateChangeLogsWithReviewer(MockTool(), options) - capture.assert_outputs(self, step.run, [{}]) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/updatechangelogswithreviewer.py b/WebKitTools/Scripts/webkitpy/tool/steps/updatechangelogswithreviewer.py deleted file mode 100644 index e46b790..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/updatechangelogswithreviewer.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 os - -from webkitpy.common.checkout.changelog import ChangeLog -from webkitpy.tool.grammar import pluralize -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import log, error - -class UpdateChangeLogsWithReviewer(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.git_commit, - Options.reviewer, - ] - - def _guess_reviewer_from_bug(self, bug_id): - patches = self._tool.bugs.fetch_bug(bug_id).reviewed_patches() - if len(patches) != 1: - log("%s on bug %s, cannot infer reviewer." % (pluralize("reviewed patch", len(patches)), bug_id)) - return None - patch = patches[0] - log("Guessing \"%s\" as reviewer from attachment %s on bug %s." % (patch.reviewer().full_name, patch.id(), bug_id)) - return patch.reviewer().full_name - - def run(self, state): - bug_id = state.get("bug_id") - if not bug_id and state.get("patch"): - bug_id = state.get("patch").bug_id() - - reviewer = self._options.reviewer - if not reviewer: - if not bug_id: - log("No bug id provided and --reviewer= not provided. Not updating ChangeLogs with reviewer.") - return - reviewer = self._guess_reviewer_from_bug(bug_id) - - if not reviewer: - log("Failed to guess reviewer from bug %s and --reviewer= not provided. Not updating ChangeLogs with reviewer." % bug_id) - return - - os.chdir(self._tool.scm().checkout_root) - for changelog_path in self.cached_lookup(state, "changelogs"): - ChangeLog(changelog_path).set_reviewer(reviewer) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/validatereviewer.py b/WebKitTools/Scripts/webkitpy/tool/steps/validatereviewer.py deleted file mode 100644 index bdf729e..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/validatereviewer.py +++ /dev/null @@ -1,71 +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 re - -from webkitpy.common.checkout.changelog import ChangeLog -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.deprecated_logging import error, log - - -# FIXME: Some of this logic should probably be unified with CommitterValidator? -class ValidateReviewer(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.git_commit, - ] - - # FIXME: This should probably move onto ChangeLogEntry - def _has_valid_reviewer(self, changelog_entry): - if changelog_entry.reviewer(): - return True - if re.search("unreviewed", changelog_entry.contents(), re.IGNORECASE): - return True - if re.search("rubber[ -]stamp", changelog_entry.contents(), re.IGNORECASE): - return True - return False - - def run(self, state): - # FIXME: For now we disable this check when a user is driving the script - # this check is too draconian (and too poorly tested) to foist upon users. - if not self._options.non_interactive: - return - # FIXME: We should figure out how to handle the current working - # directory issue more globally. - os.chdir(self._tool.scm().checkout_root) - for changelog_path in self.cached_lookup(state, "changelogs"): - changelog_entry = ChangeLog(changelog_path).latest_entry() - if self._has_valid_reviewer(changelog_entry): - continue - reviewer_text = changelog_entry.reviewer_text() - if reviewer_text: - log("%s found in %s does not appear to be a valid reviewer according to committers.py." % (reviewer_text, changelog_path)) - error('%s neither lists a valid reviewer nor contains the string "Unreviewed" or "Rubber stamp" (case insensitive).' % changelog_path) diff --git a/WebKitTools/Scripts/webkitpy/tool/steps/validatereviewer_unittest.py b/WebKitTools/Scripts/webkitpy/tool/steps/validatereviewer_unittest.py deleted file mode 100644 index d9b856a..0000000 --- a/WebKitTools/Scripts/webkitpy/tool/steps/validatereviewer_unittest.py +++ /dev/null @@ -1,57 +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.changelog import ChangeLogEntry -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.tool.steps.validatereviewer import ValidateReviewer - -class ValidateReviewerTest(unittest.TestCase): - _boilerplate_entry = '''2009-08-19 Eric Seidel <eric@webkit.org> - - REVIEW_LINE - - * Scripts/bugzilla-tool: -''' - - def _test_review_text(self, step, text, expected): - contents = self._boilerplate_entry.replace("REVIEW_LINE", text) - entry = ChangeLogEntry(contents) - self.assertEqual(step._has_valid_reviewer(entry), expected) - - def test_has_valid_reviewer(self): - step = ValidateReviewer(MockTool(), MockOptions()) - self._test_review_text(step, "Reviewed by Eric Seidel.", True) - self._test_review_text(step, "Reviewed by Eric Seidel", True) # Not picky about the '.' - self._test_review_text(step, "Reviewed by Eric.", False) - self._test_review_text(step, "Reviewed by Eric C Seidel.", False) - self._test_review_text(step, "Rubber-stamped by Eric.", True) - self._test_review_text(step, "Rubber stamped by Eric.", True) - self._test_review_text(step, "Unreviewed build fix.", True) |