diff options
| author | Steve Block <steveblock@google.com> | 2011-05-18 13:36:51 +0100 |
|---|---|---|
| committer | Steve Block <steveblock@google.com> | 2011-05-24 15:38:28 +0100 |
| commit | 2fc2651226baac27029e38c9d6ef883fa32084db (patch) | |
| tree | e396d4bf89dcce6ed02071be66212495b1df1dec /Tools/Scripts/webkitpy/common | |
| parent | b3725cedeb43722b3b175aaeff70552e562d2c94 (diff) | |
| download | external_webkit-2fc2651226baac27029e38c9d6ef883fa32084db.zip external_webkit-2fc2651226baac27029e38c9d6ef883fa32084db.tar.gz external_webkit-2fc2651226baac27029e38c9d6ef883fa32084db.tar.bz2 | |
Merge WebKit at r78450: Initial merge by git.
Change-Id: I6d3e5f1f868ec266a0aafdef66182ddc3f265dc1
Diffstat (limited to 'Tools/Scripts/webkitpy/common')
32 files changed, 1131 insertions, 167 deletions
diff --git a/Tools/Scripts/webkitpy/common/checkout/api.py b/Tools/Scripts/webkitpy/common/checkout/api.py index a87bb5a..170b822 100644 --- a/Tools/Scripts/webkitpy/common/checkout/api.py +++ b/Tools/Scripts/webkitpy/common/checkout/api.py @@ -33,6 +33,7 @@ from webkitpy.common.config import urls from webkitpy.common.checkout.changelog import ChangeLog from webkitpy.common.checkout.commitinfo import CommitInfo from webkitpy.common.checkout.scm import CommitMessage +from webkitpy.common.checkout.deps import DEPS 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 @@ -148,6 +149,9 @@ class Checkout(object): except ScriptError, e: pass # We might not have ChangeLogs. + def chromium_deps(self): + return DEPS(os.path.join(self._scm.checkout_root, "Source", "WebKit", "chromium", "DEPS")) + 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. diff --git a/Tools/Scripts/webkitpy/common/checkout/changelog.py b/Tools/Scripts/webkitpy/common/checkout/changelog.py index 07f905d..c81318c 100644 --- a/Tools/Scripts/webkitpy/common/checkout/changelog.py +++ b/Tools/Scripts/webkitpy/common/checkout/changelog.py @@ -36,9 +36,7 @@ import textwrap from webkitpy.common.system.deprecated_logging import log from webkitpy.common.config.committers import CommitterList -from webkitpy.common.config import urls from webkitpy.common.net.bugzilla import parse_bug_id -from webkitpy.tool.grammar import join_with_separators class ChangeLogEntry(object): @@ -145,29 +143,14 @@ class ChangeLog(object): 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_list, reason, bug_url): - message = "Unreviewed, rolling out %s.\n" % join_with_separators(['r' + str(revision) for revision in revision_list]) - for revision in revision_list: - message += "%s\n" % urls.view_revision_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_list, reason, bug_url=None): + def update_with_unreviewed_message(self, message): 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_list, - reason, - bug_url) + message_lines = self._wrap_lines(message) print reviewed_by_regexp.sub(message_lines, line), # Remove all the ChangeLog boilerplate between the Reviewed by # line and the first changed file. diff --git a/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py b/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py index 20c6cfa..299d509 100644 --- a/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py +++ b/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py @@ -142,89 +142,3 @@ class ChangeLogTest(unittest.TestCase): 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: -''' - - _multiple_revert_entry_with_bug_url = '''2009-08-19 Eric Seidel <eric@webkit.org> - - Unreviewed, rolling out r12345, r12346, and r12347. - http://trac.webkit.org/changeset/12345 - http://trac.webkit.org/changeset/12346 - http://trac.webkit.org/changeset/12347 - http://example.com/123 - - Reason - - * Scripts/bugzilla-tool: -''' - - _multiple_revert_entry_without_bug_url = '''2009-08-19 Eric Seidel <eric@webkit.org> - - Unreviewed, rolling out r12345, r12346, and r12347. - http://trac.webkit.org/changeset/12345 - http://trac.webkit.org/changeset/12346 - http://trac.webkit.org/changeset/12347 - - 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) - self._assert_update_for_revert_output([[12345, 12346, 12347], "Reason"], self._multiple_revert_entry_without_bug_url) - self._assert_update_for_revert_output([[12345, 12346, 12347], "Reason", "http://example.com/123"], self._multiple_revert_entry_with_bug_url) - - -if __name__ == '__main__': - unittest.main() diff --git a/Tools/Scripts/webkitpy/common/checkout/deps.py b/Tools/Scripts/webkitpy/common/checkout/deps.py new file mode 100644 index 0000000..6b87ff1 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/checkout/deps.py @@ -0,0 +1,61 @@ +# Copyright (C) 2011, 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 +import os.path +import re +import textwrap + + +class DEPS(object): + + _variable_regexp = r"\s+'%s':\s+'(?P<value>\d+)'" + + def __init__(self, path): + self._path = path + + def read_variable(self, name): + pattern = re.compile(self._variable_regexp % name) + for line in fileinput.FileInput(self._path): + match = pattern.match(line) + if match: + return int(match.group("value")) + + def write_variable(self, name, value): + pattern = re.compile(self._variable_regexp % name) + replacement_line = " '%s': '%s'" % (name, value) + # inplace=1 creates a backup file and re-directs stdout to the file + for line in fileinput.FileInput(self._path, inplace=1): + if pattern.match(line): + print replacement_line + continue + # Trailing comma suppresses printing newline + print line, diff --git a/Tools/Scripts/webkitpy/common/checkout/scm.py b/Tools/Scripts/webkitpy/common/checkout/scm.py index 421c0dc..3fa2db5 100644 --- a/Tools/Scripts/webkitpy/common/checkout/scm.py +++ b/Tools/Scripts/webkitpy/common/checkout/scm.py @@ -34,10 +34,10 @@ 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 -import webkitpy.common.system.ospath as ospath from webkitpy.common.memoized import memoized +from webkitpy.common.system.deprecated_logging import error, log +from webkitpy.common.system.executive import Executive, run_command, ScriptError +from webkitpy.common.system import ospath def find_checkout_root(): @@ -746,6 +746,22 @@ class Git(SCM): def display_name(self): return "git" + def prepend_svn_revision(self, diff): + revision = None + tries = 0 + while not revision and tries < 10: + # If the git checkout is not tracking an SVN repo, then svn_revision_from_git_commit throws. + try: + revision = self.svn_revision_from_git_commit('HEAD~' + str(tries)) + except: + return diff + tries += 1 + + if not revision: + return diff + + return "Subversion Revision: " + str(revision) + '\n' + diff + def create_patch(self, git_commit=None, changed_files=None): """Returns a byte array (str()) representing the patch file. Patch files are effectively binary since they may contain @@ -753,7 +769,7 @@ class Git(SCM): command = ['git', 'diff', '--binary', "--no-ext-diff", "--full-index", "-M", self.merge_base(git_commit), "--"] if changed_files: command += changed_files - return self.run(command, decode_output=False, cwd=self.checkout_root) + return self.prepend_svn_revision(self.run(command, 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. diff --git a/Tools/Scripts/webkitpy/common/checkout/scm_unittest.py b/Tools/Scripts/webkitpy/common/checkout/scm_unittest.py index 64122b4..decfae0 100644 --- a/Tools/Scripts/webkitpy/common/checkout/scm_unittest.py +++ b/Tools/Scripts/webkitpy/common/checkout/scm_unittest.py @@ -810,6 +810,16 @@ class GitTest(SCMTest): run_command(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote2']) self.assertEqual(self.tracking_scm.remote_branch_ref(), 'remote1') + def test_create_patch(self): + write_into_file_at_path('test_file_commit1', 'contents') + run_command(['git', 'add', 'test_file_commit1']) + scm = detect_scm_system(self.untracking_checkout_path) + scm.commit_locally_with_message('message') + + patch = scm.create_patch() + self.assertFalse(re.search(r'Subversion Revision:', patch)) + + class GitSVNTest(SCMTest): def _setup_git_checkout(self): @@ -1126,6 +1136,7 @@ class GitSVNTest(SCMTest): patch = scm.create_patch() self.assertTrue(re.search(r'test_file_commit2', patch)) self.assertTrue(re.search(r'test_file_commit1', patch)) + self.assertTrue(re.search(r'Subversion Revision: 5', patch)) def test_create_patch_with_changed_files(self): self._one_local_commit_plus_working_copy_changes() diff --git a/Tools/Scripts/webkitpy/common/config/committers.py b/Tools/Scripts/webkitpy/common/config/committers.py index f7d59fe..5c571ab 100644 --- a/Tools/Scripts/webkitpy/common/config/committers.py +++ b/Tools/Scripts/webkitpy/common/config/committers.py @@ -91,16 +91,17 @@ committers_unable_to_review = [ Committer("Cameron McCormack", "cam@webkit.org", "heycam"), Committer("Carlos Garcia Campos", ["cgarcia@igalia.com", "carlosgc@gnome.org", "carlosgc@webkit.org"], "KaL"), Committer("Carol Szabo", "carol.szabo@nokia.com"), - Committer("Chang Shu", "Chang.Shu@nokia.com"), + Committer("Chang Shu", ["Chang.Shu@nokia.com", "cshu@webkit.org"], "cshu"), 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("Daniel Cheng", "dcheng@chromium.org", "dcheng"), 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("Dirk Pranke", "dpranke@chromium.org", "dpranke"), Committer("Drew Wilson", "atwilson@chromium.org", "atwilson"), Committer("Eli Fidler", "eli@staikos.net", "QBin"), Committer("Enrica Casucci", "enrica@apple.com"), @@ -113,6 +114,7 @@ committers_unable_to_review = [ Committer("Feng Qian", "feng@chromium.org"), Committer("Fumitoshi Ukai", "ukai@chromium.org", "ukai"), Committer("Gabor Loki", "loki@webkit.org", "loki04"), + Committer("Gabor Rapcsanyi", ["rgabor@webkit.org", "rgabor@inf.u-szeged.hu"], "rgabor"), 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"), @@ -121,6 +123,7 @@ committers_unable_to_review = [ 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("Ivan Krsti\u0107", "ike@apple.com"), 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"), @@ -137,6 +140,7 @@ committers_unable_to_review = [ Committer("John Gregg", ["johnnyg@google.com", "johnnyg@chromium.org"], "johnnyg"), Committer("John Knottenbelt", ["jknotten@chromium.org"], "jknotten"), Committer("Johnny Ding", ["jnd@chromium.org", "johnnyding.webkit@gmail.com"], "johnnyding"), + Committer("Joone Hur", ["joone.hur@collabora.co.uk", "joone@kldp.org", "joone@webkit.org"], "joone"), 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"]), diff --git a/Tools/Scripts/webkitpy/common/config/urls.py b/Tools/Scripts/webkitpy/common/config/urls.py index dfa6d69..ddaef97 100644 --- a/Tools/Scripts/webkitpy/common/config/urls.py +++ b/Tools/Scripts/webkitpy/common/config/urls.py @@ -34,5 +34,6 @@ def view_source_url(local_path): def view_revision_url(revision_number): return "http://trac.webkit.org/changeset/%s" % revision_number +chromium_lkgr_url = "http://chromium-status.appspot.com/lkgr" contribution_guidelines = "http://webkit.org/coding/contributing.html" diff --git a/Tools/Scripts/webkitpy/common/net/buildbot/buildbot.py b/Tools/Scripts/webkitpy/common/net/buildbot/buildbot.py index 3cb6da5..76cd31d 100644 --- a/Tools/Scripts/webkitpy/common/net/buildbot/buildbot.py +++ b/Tools/Scripts/webkitpy/common/net/buildbot/buildbot.py @@ -266,10 +266,11 @@ class BuildBot(object): # 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. + "SnowLeopard.*\(Test", + "SnowLeopard.*\(WebKit2 Test", "Leopard", - "Tiger", "Windows.*Build", + "EFL", "GTK.*32", "GTK.*64.*Debug", # Disallow the 64-bit Release bot which is broken. "Qt", diff --git a/Tools/Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py b/Tools/Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py index 57290d1..f158827 100644 --- a/Tools/Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py +++ b/Tools/Scripts/webkitpy/common/net/buildbot/buildbot_unittest.py @@ -222,7 +222,6 @@ class BuildBotTest(unittest.TestCase): # 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)', }, @@ -256,22 +255,23 @@ class BuildBotTest(unittest.TestCase): name_regexps = [ "SnowLeopard.*Build", "SnowLeopard.*\(Test", + "SnowLeopard.*\(WebKit2 Test", "Leopard", - "Tiger", "Windows.*Build", + "EFL", "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'SnowLeopard Intel Release (WebKit2 Tests)', }, {'name': u'Windows Release (Build)', }, {'name': u'Windows Debug (Build)', }, {'name': u'GTK Linux 32-bit Release', }, diff --git a/Tools/Scripts/webkitpy/common/net/irc/ircbot.py b/Tools/Scripts/webkitpy/common/net/irc/ircbot.py index f742867..061a43c 100644 --- a/Tools/Scripts/webkitpy/common/net/irc/ircbot.py +++ b/Tools/Scripts/webkitpy/common/net/irc/ircbot.py @@ -26,7 +26,7 @@ # (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.config import irc as config_irc from webkitpy.common.thread.messagepump import MessagePump, MessagePumpDelegate from webkitpy.thirdparty.autoinstalled.irc import ircbot diff --git a/Tools/Scripts/webkitpy/common/net/layouttestresults.py b/Tools/Scripts/webkitpy/common/net/layouttestresults.py index 28caad4..249ecc9 100644 --- a/Tools/Scripts/webkitpy/common/net/layouttestresults.py +++ b/Tools/Scripts/webkitpy/common/net/layouttestresults.py @@ -51,11 +51,13 @@ class LayoutTestResults(object): 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):' + webprocess_crash_key = u'Tests that caused the Web process to crash:' expected_keys = [ stderr_key, fail_key, crash_key, + webprocess_crash_key, timeout_key, missing_key, ] @@ -87,6 +89,8 @@ class LayoutTestResults(object): return cls._failures_from_fail_row(row) if table_title == cls.crash_key: return [test_failures.FailureCrash()] + if table_title == cls.webprocess_crash_key: + return [test_failures.FailureCrash()] if table_title == cls.timeout_key: return [test_failures.FailureTimeout()] if table_title == cls.missing_key: diff --git a/Tools/Scripts/webkitpy/common/net/testoutput.py b/Tools/Scripts/webkitpy/common/net/testoutput.py new file mode 100644 index 0000000..37c1445 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/net/testoutput.py @@ -0,0 +1,179 @@ +#!/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. + +import os +import re + + +class NaiveImageDiffer(object): + def same_image(self, img1, img2): + return img1 == img2 + + +class TestOutput(object): + """Represents the output that a single layout test generates when it is run + on a particular platform. + Note that this is the raw output that is produced when the layout test is + run, not the results of the subsequent comparison between that output and + the expected output.""" + def __init__(self, platform, output_type, files): + self._output_type = output_type + self._files = files + file = files[0] # Pick some file to do test name calculation. + self._name = self._extract_test_name(file.name()) + self._is_actual = '-actual.' in file.name() + + self._platform = platform or self._extract_platform(file.name()) + + def _extract_platform(self, filename): + """Calculates the platform from the name of the file if it isn't known already""" + path = re.split(os.path.sep, filename) + if 'platform' in path: + return path[path.index('platform') + 1] + return None + + def _extract_test_name(self, filename): + path = re.split(os.path.sep, filename) + if 'LayoutTests' in path: + path = path[1 + path.index('LayoutTests'):] + if 'layout-test-results' in path: + path = path[1 + path.index('layout-test-results'):] + if 'platform' in path: + path = path[2 + path.index('platform'):] + + filename = path[-1] + filename = re.sub('-expected\..*$', '', filename) + filename = re.sub('-actual\..*$', '', filename) + path[-1] = filename + return os.path.sep.join(path) + + def save_to(self, path): + """Have the files in this TestOutput write themselves to the disk at the specified location.""" + for file in self._files: + file.save_to(path) + + def is_actual(self): + """Is this output the actual output of a test? (As opposed to expected output.)""" + return self._is_actual + + def name(self): + """The name of this test (doesn't include extension)""" + return self._name + + def __eq__(self, other): + return (other != None and + self.name() == other.name() and + self.type() == other.type() and + self.platform() == other.platform() and + self.is_actual() == other.is_actual() and + self.same_content(other)) + + def __hash__(self): + return hash(str(self.name()) + str(self.type()) + str(self.platform())) + + def is_new_baseline_for(self, other): + return (self.name() == other.name() and + self.type() == other.type() and + self.platform() == other.platform() and + self.is_actual() and + (not other.is_actual())) + + def __str__(self): + actual_str = '[A] ' if self.is_actual() else '' + return "TestOutput[%s/%s] %s%s" % (self._platform, self._output_type, actual_str, self.name()) + + def type(self): + return self._output_type + + def platform(self): + return self._platform + + def _path_to_platform(self): + """Returns the path that tests for this platform are stored in.""" + if self._platform is None: + return "" + else: + return os.path.join("self._platform", self._platform) + + def _save_expected_result(self, file, path): + path = os.path.join(path, self._path_to_platform()) + extension = os.path.splitext(file.name())[1] + filename = self.name() + '-expected' + extension + file.save_to(path, filename) + + def save_expected_results(self, path_to_layout_tests): + """Save the files of this TestOutput to the appropriate directory + inside the LayoutTests directory. Typically this means that these files + will be saved in "LayoutTests/platform/<platform>/, or simply + LayoutTests if the platform is None.""" + for file in self._files: + self._save_expected_result(file, path_to_layout_tests) + + def delete(self): + """Deletes the files that comprise this TestOutput from disk. This + fails if the files are virtual files (eg: the files may reside inside a + remote zip file).""" + for file in self._files: + file.delete() + + +class TextTestOutput(TestOutput): + """Represents a text output of a single test on a single platform""" + def __init__(self, platform, text_file): + self._text_file = text_file + TestOutput.__init__(self, platform, 'text', [text_file]) + + def same_content(self, other): + return self._text_file.contents() == other._text_file.contents() + + def retarget(self, platform): + return TextTestOutput(platform, self._text_file) + + +class ImageTestOutput(TestOutput): + image_differ = NaiveImageDiffer() + """Represents an image output of a single test on a single platform""" + def __init__(self, platform, image_file, checksum_file): + self._checksum_file = checksum_file + self._image_file = image_file + files = filter(bool, [self._checksum_file, self._image_file]) + TestOutput.__init__(self, platform, 'image', files) + + def has_checksum(self): + return self._checksum_file is not None + + def same_content(self, other): + # FIXME This should not assume that checksums are up to date. + if self.has_checksum() and other.has_checksum(): + return self._checksum_file.contents() == other._checksum_file.contents() + else: + self_contents = self._image_file.contents() + other_contents = other._image_file.contents() + return ImageTestOutput.image_differ.same_image(self_contents, other_contents) + + def retarget(self, platform): + return ImageTestOutput(platform, self._image_file, self._checksum_file) + + def checksum(self): + return self._checksum_file.contents() diff --git a/Tools/Scripts/webkitpy/common/net/testoutput_unittest.py b/Tools/Scripts/webkitpy/common/net/testoutput_unittest.py new file mode 100644 index 0000000..ad38ca6 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/net/testoutput_unittest.py @@ -0,0 +1,133 @@ +#!/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. + +import re +import testoutput +import unittest + + +class FakeFile(object): + def __init__(self, filename, contents="fake contents"): + self._filename = filename + self._contents = contents + + def name(self): + return self._filename + + def contents(self): + return self._contents + + +class FakeTestOutput(testoutput.TestOutput): + def __init__(self, platform, output_type, contents, is_expected=False): + self._output_type = output_type + self._contents = contents + self._is_expected = is_expected + actual = 'actual' + if is_expected: + actual = 'expected' + test_name = 'anonymous-test-%s.txt' % actual + file = FakeFile(test_name, contents) + super(FakeTestOutput, self).__init__(platform, output_type, [file]) + + def contents(self): + return self._contents + + def retarget(self, platform): + return FakeTestOutput(platform, self._output_type, self._contents, self._is_expected) + + +class TestOutputTest(unittest.TestCase): + def _check_name(self, filename, expected_test_name): + r = testoutput.TextTestOutput(None, FakeFile(filename)) + self.assertEquals(expected_test_name, r.name()) + + def _check_platform(self, filename, expected_platform): + r = testoutput.TextTestOutput(None, FakeFile(filename)) + self.assertEquals(expected_platform, r.platform()) + + def test_extracts_name_correctly(self): + self._check_name('LayoutTests/fast/dom/a-expected.txt', 'fast/dom/a') + self._check_name('LayoutTests/fast/dom/a-actual.txt', 'fast/dom/a') + self._check_name('LayoutTests/platform/win/fast/a-expected.txt', 'fast/a') + self._check_name('LayoutTests/platform/win/fast/a-expected.checksum', 'fast/a') + self._check_name('fast/dom/test-expected.txt', 'fast/dom/test') + self._check_name('layout-test-results/fast/a-actual.checksum', 'fast/a') + + def test_extracts_platform_correctly(self): + self._check_platform('LayoutTests/platform/win/fast/a-expected.txt', 'win') + self._check_platform('platform/win/fast/a-expected.txt', 'win') + self._check_platform('platform/mac/fast/a-expected.txt', 'mac') + self._check_platform('fast/a-expected.txt', None) + + def test_outputs_from_an_actual_file_are_marked_as_such(self): + r = testoutput.TextTestOutput(None, FakeFile('test-actual.txt')) + self.assertTrue(r.is_actual()) + + def test_outputs_from_an_expected_file_are_not_actual(self): + r = testoutput.TextTestOutput(None, FakeFile('test-expected.txt')) + self.assertFalse(r.is_actual()) + + def test_is_new_baseline_for(self): + expected = testoutput.TextTestOutput('mac', FakeFile('test-expected.txt')) + actual = testoutput.TextTestOutput('mac', FakeFile('test-actual.txt')) + self.assertTrue(actual.is_new_baseline_for(expected)) + self.assertFalse(expected.is_new_baseline_for(actual)) + + def test__eq__(self): + r1 = testoutput.TextTestOutput('mac', FakeFile('test-expected.txt', 'contents')) + r2 = testoutput.TextTestOutput('mac', FakeFile('test-expected.txt', 'contents')) + r3 = testoutput.TextTestOutput('win', FakeFile('test-expected.txt', 'contents')) + + self.assertEquals(r1, r2) + self.assertEquals(r1, r2.retarget('mac')) + self.assertNotEquals(r1, r2.retarget('win')) + + def test__hash__(self): + r1 = testoutput.TextTestOutput('mac', FakeFile('test-expected.txt', 'contents')) + r2 = testoutput.TextTestOutput('mac', FakeFile('test-expected.txt', 'contents')) + r3 = testoutput.TextTestOutput(None, FakeFile('test-expected.txt', None)) + + x = set([r1, r2]) + self.assertEquals(1, len(set([r1, r2]))) + self.assertEquals(2, len(set([r1, r2, r3]))) + + def test_image_diff_is_invoked_for_image_outputs_without_checksum(self): + r1 = testoutput.ImageTestOutput('mac', FakeFile('test-expected.png', 'asdf'), FakeFile('test-expected.checksum', 'check')) + r2 = testoutput.ImageTestOutput('mac', FakeFile('test-expected.png', 'asdf'), None) + + # Default behaviour is to just compare on image contents. + self.assertTrue(r1.same_content(r2)) + + class AllImagesAreDifferent(object): + def same_image(self, image1, image2): + return False + + # But we can install other image differs. + testoutput.ImageTestOutput.image_differ = AllImagesAreDifferent() + + self.assertFalse(r1.same_content(r2)) + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/Scripts/webkitpy/common/net/testoutputset.py b/Tools/Scripts/webkitpy/common/net/testoutputset.py new file mode 100644 index 0000000..4074686 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/net/testoutputset.py @@ -0,0 +1,130 @@ +#!/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. + +from webkitpy.common.system.directoryfileset import DirectoryFileSet +from webkitpy.common.system.zipfileset import ZipFileSet +import re +import testoutput +import urllib + + +class TestOutputSet(object): + def __init__(self, name, platform, zip_file, **kwargs): + self._name = name + self._platform = platform + self._zip_file = zip_file + self._include_expected = kwargs.get('include_expected', True) + + @classmethod + def from_zip_url(cls, platform, zip_path): + return TestOutputSet('local zip %s builder' % platform, platform, ZipFileSet(zip_path)) + + @classmethod + def from_zip(cls, platform, zip): + return TestOutputSet('local zip %s builder' % platform, platform, zip) + + @classmethod + def from_zip_map(cls, zip_map): + output_sets = [] + for k, v in zip_map.items(): + output_sets.append(TestOutputSet.from_zip(k, v)) + return AggregateTestOutputSet(output_sets) + + @classmethod + def from_path(self, path, platform=None): + return TestOutputSet('local %s builder' % platform, platform, DirectoryFileSet(path)) + + def name(self): + return self._name + + def set_platform(self, platform): + self._platform = platform + + def files(self): + return [self._zip_file.open(filename) for filename in self._zip_file.namelist()] + + def _extract_output_files(self, name, exact_match): + name_matcher = re.compile(name) + actual_matcher = re.compile(r'-actual\.') + expected_matcher = re.compile(r'-expected\.') + + checksum_files = [] + text_files = [] + image_files = [] + for output_file in self.files(): + name_match = name_matcher.search(output_file.name()) + actual_match = actual_matcher.search(output_file.name()) + expected_match = expected_matcher.search(output_file.name()) + if not (name_match and (actual_match or (self._include_expected and expected_match))): + continue + if output_file.name().endswith('.checksum'): + checksum_files.append(output_file) + elif output_file.name().endswith('.txt'): + text_files.append(output_file) + elif output_file.name().endswith('.png'): + image_files.append(output_file) + + return (checksum_files, text_files, image_files) + + def _extract_file_with_name(self, name, files): + for file in files: + if file.name() == name: + return file + return None + + def _make_output_from_image(self, image_file, checksum_files): + checksum_file_name = re.sub('\.png', '.checksum', image_file.name()) + checksum_file = self._extract_file_with_name(checksum_file_name, checksum_files) + return testoutput.ImageTestOutput(self._platform, image_file, checksum_file) + + def outputs_for(self, name, **kwargs): + target_type = kwargs.get('target_type', None) + exact_match = kwargs.get('exact_match', False) + if re.search(r'\.x?html', name): + name = name[:name.rindex('.')] + + (checksum_files, text_files, image_files) = self._extract_output_files(name, exact_match) + + outputs = [self._make_output_from_image(image_file, checksum_files) for image_file in image_files] + + outputs += [testoutput.TextTestOutput(self._platform, text_file) for text_file in text_files] + + if exact_match: + outputs = filter(lambda output: output.name() == name, outputs) + + outputs = filter(lambda r: target_type in [None, r.type()], outputs) + + return outputs + + +class AggregateTestOutputSet(object): + """Set of test outputs from a list of builders""" + def __init__(self, builders): + self._builders = builders + + def outputs_for(self, name, **kwargs): + return sum([builder.outputs_for(name, **kwargs) for builder in self._builders], []) + + def builders(self): + return self._builders diff --git a/Tools/Scripts/webkitpy/common/net/testoutputset_unittest.py b/Tools/Scripts/webkitpy/common/net/testoutputset_unittest.py new file mode 100644 index 0000000..a70a539 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/net/testoutputset_unittest.py @@ -0,0 +1,125 @@ +#!/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. + +from webkitpy.common.system.zip_mock import MockZip +import testoutputset +import unittest + + +class TestOutputSetTest(unittest.TestCase): + def _outputset_with_zip(self, zip, **kwargs): + return testoutputset.TestOutputSet('<fake-outputset>', '<fake-platform>', zip, **kwargs) + + def test_text_files_get_interpreted_as_text_outputs(self): + zip = MockZip() + zip.insert('fast/dom/some-test-actual.txt', 'actual outputs') + b = self._outputset_with_zip(zip) + self.assertEquals(1, len(b.outputs_for('fast/dom/some-test'))) + self.assertEquals('fast/dom/some-test', b.outputs_for('fast/dom/some-test.html')[0].name()) + + def test_image_and_checksum_files_get_interpreted_as_a_single_image_output(self): + zip = MockZip() + zip.insert('fast/dom/some-test-actual.checksum', 'abc123') + zip.insert('fast/dom/some-test-actual.png', '<image data>') + b = self._outputset_with_zip(zip) + outputs = b.outputs_for('fast/dom/some-test') + self.assertEquals(1, len(outputs)) + output = outputs[0] + self.assertEquals('image', output.type()) + self.assertEquals('abc123', output.checksum()) + + def test_multiple_image_outputs_are_detected(self): + zip = MockZip() + zip.insert('platform/win/fast/dom/some-test-actual.checksum', 'checksum1') + zip.insert('platform/win/fast/dom/some-test-actual.png', '<image data 1>') + zip.insert('platform/mac/fast/dom/some-test-actual.checksum', 'checksum2') + zip.insert('platform/mac/fast/dom/some-test-actual.png', '<image data 2>') + b = self._outputset_with_zip(zip) + outputs = b.outputs_for('fast/dom/some-test') + self.assertEquals(2, len(outputs)) + self.assertFalse(outputs[0].same_content(outputs[1])) + + def test_aggregate_output_set_correctly_retrieves_tests_from_multiple_output_sets(self): + outputset1_zip = MockZip() + outputset1_zip.insert('fast/dom/test-actual.txt', 'linux text output') + outputset1 = testoutputset.TestOutputSet('linux-outputset', 'linux', outputset1_zip) + outputset2_zip = MockZip() + outputset2_zip.insert('fast/dom/test-actual.txt', 'windows text output') + outputset2 = testoutputset.TestOutputSet('win-outputset', 'win', outputset2_zip) + + b = testoutputset.AggregateTestOutputSet([outputset1, outputset2]) + self.assertEquals(2, len(b.outputs_for('fast/dom/test'))) + + def test_can_infer_platform_from_path_if_none_provided(self): + zip = MockZip() + zip.insert('platform/win/some-test-expected.png', '<image data>') + zip.insert('platform/win/some-test-expected.checksum', 'abc123') + b = testoutputset.TestOutputSet('local LayoutTests outputset', None, zip) + + outputs = b.outputs_for('some-test') + self.assertEquals(1, len(outputs)) + self.assertEquals('win', outputs[0].platform()) + + def test_test_extension_is_ignored(self): + zip = MockZip() + zip.insert('test/test-a-actual.txt', 'actual outputs') + b = self._outputset_with_zip(zip) + outputs = b.outputs_for('test/test-a.html') + self.assertEquals(1, len(outputs)) + self.assertEquals('test/test-a', outputs[0].name()) + + def test_existing_outputs_are_marked_as_such(self): + zip = MockZip() + zip.insert('test/test-a-expected.txt', 'expected outputs') + b = self._outputset_with_zip(zip) + outputs = b.outputs_for('test/test-a.html') + self.assertEquals(1, len(outputs)) + self.assertFalse(outputs[0].is_actual()) + + def test_only_returns_outputs_of_specified_type(self): + zip = MockZip() + zip.insert('test/test-a-expected.txt', 'expected outputs') + zip.insert('test/test-a-expected.checksum', 'expected outputs') + zip.insert('test/test-a-expected.png', 'expected outputs') + b = self._outputset_with_zip(zip) + + outputs = b.outputs_for('test/test-a.html') + text_outputs = b.outputs_for('test/test-a.html', target_type='text') + image_outputs = b.outputs_for('test/test-a.html', target_type='image') + + self.assertEquals(2, len(outputs)) + self.assertEquals(1, len(text_outputs)) + self.assertEquals(1, len(image_outputs)) + self.assertEquals('text', text_outputs[0].type()) + self.assertEquals('image', image_outputs[0].type()) + + def test_exclude_expected_outputs_works(self): + zip = MockZip() + zip.insert('test-expected.txt', 'expected outputs stored on server for some reason') + b = self._outputset_with_zip(zip, include_expected=False) + outputs = b.outputs_for('test', target_type=None) + self.assertEquals(0, len(outputs)) + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/Scripts/webkitpy/common/system/directoryfileset.py b/Tools/Scripts/webkitpy/common/system/directoryfileset.py index 11acaf4..a5cab0e 100644 --- a/Tools/Scripts/webkitpy/common/system/directoryfileset.py +++ b/Tools/Scripts/webkitpy/common/system/directoryfileset.py @@ -21,14 +21,8 @@ # (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 shutil - from webkitpy.common.system.fileset import FileSetFileHandle from webkitpy.common.system.filesystem import FileSystem -import webkitpy.common.system.ospath as ospath class DirectoryFileSet(object): @@ -36,8 +30,8 @@ class DirectoryFileSet(object): def __init__(self, path, filesystem=None): self._path = path self._filesystem = filesystem or FileSystem() - if not self._path.endswith(os.path.sep): - self._path += os.path.sep + if not self._path.endswith(self._filesystem.sep): + self._path += self._filesystem.sep def _full_path(self, filename): assert self._is_under(self._path, filename) @@ -52,7 +46,7 @@ class DirectoryFileSet(object): return self._filesystem.files_under(self._path) def _is_under(self, dir, filename): - return bool(ospath.relpath(self._filesystem.join(dir, filename), dir)) + return bool(self._filesystem.relpath(self._filesystem.join(dir, filename), dir)) def open(self, filename): return FileSetFileHandle(self, filename, self._filesystem) @@ -69,7 +63,7 @@ class DirectoryFileSet(object): dest = self._filesystem.join(path, filename) # As filename may have slashes in it, we must ensure that the same # directory hierarchy exists at the output path. - self._filesystem.maybe_make_directory(os.path.split(dest)[0]) + self._filesystem.maybe_make_directory(self._filesystem.dirname(dest)) self._filesystem.copyfile(src, dest) def delete(self, filename): diff --git a/Tools/Scripts/webkitpy/common/system/fileset.py b/Tools/Scripts/webkitpy/common/system/fileset.py index 22f7c4d..598c1c5 100644 --- a/Tools/Scripts/webkitpy/common/system/fileset.py +++ b/Tools/Scripts/webkitpy/common/system/fileset.py @@ -22,7 +22,6 @@ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import with_statement -import os from webkitpy.common.system.filesystem import FileSystem @@ -38,6 +37,9 @@ class FileSetFileHandle(object): def __str__(self): return "%s:%s" % (self._fileset, self._filename) + def close(self): + pass + def contents(self): if self._contents is None: self._contents = self._fileset.read(self._filename) @@ -61,4 +63,4 @@ class FileSetFileHandle(object): return self._filename def splitext(self): - return os.path.splitext(self.name()) + return self._filesystem.splitext(self.name()) diff --git a/Tools/Scripts/webkitpy/common/system/filesystem.py b/Tools/Scripts/webkitpy/common/system/filesystem.py index 05513a9..b876807 100644 --- a/Tools/Scripts/webkitpy/common/system/filesystem.py +++ b/Tools/Scripts/webkitpy/common/system/filesystem.py @@ -39,13 +39,20 @@ import shutil import tempfile import time +from webkitpy.common.system import ospath + class FileSystem(object): """FileSystem interface for webkitpy. Unless otherwise noted, all paths are allowed to be either absolute or relative.""" def __init__(self): - self.sep = os.sep + self._sep = os.sep + + def _get_sep(self): + return self._sep + + sep = property(_get_sep, doc="pathname separator") def abspath(self, path): return os.path.abspath(path) @@ -165,8 +172,8 @@ class FileSystem(object): if e.errno != errno.EEXIST: raise - def move(self, src, dest): - shutil.move(src, dest) + def move(self, source, destination): + shutil.move(source, destination) def mtime(self, path): return os.stat(path).st_mtime @@ -200,6 +207,9 @@ class FileSystem(object): with codecs.open(path, 'r', 'utf8') as f: return f.read() + def relpath(self, path, start='.'): + return ospath.relpath(path, start) + class _WindowsError(exceptions.OSError): """Fake exception for Linux and Mac.""" pass diff --git a/Tools/Scripts/webkitpy/common/system/filesystem_mock.py b/Tools/Scripts/webkitpy/common/system/filesystem_mock.py index 0004944..aa79a8c 100644 --- a/Tools/Scripts/webkitpy/common/system/filesystem_mock.py +++ b/Tools/Scripts/webkitpy/common/system/filesystem_mock.py @@ -28,9 +28,11 @@ import errno import os -import path import re +from webkitpy.common.system import path +from webkitpy.common.system import ospath + class MockFileSystem(object): def __init__(self, files=None): @@ -44,17 +46,23 @@ class MockFileSystem(object): """ self.files = files or {} self.written_files = {} - self.sep = '/' + self._sep = '/' self.current_tmpno = 0 + def _get_sep(self): + return self._sep + + sep = property(_get_sep, doc="pathname separator") + def _raise_not_found(self, path): raise IOError(errno.ENOENT, path, os.strerror(errno.ENOENT)) def _split(self, path): - idx = path.rfind('/') - return (path[0:idx], path[idx + 1:]) + return path.rsplit(self.sep, 1) def abspath(self, path): + if path.endswith(self.sep): + return path[:-1] return path def basename(self, path): @@ -69,6 +77,7 @@ class MockFileSystem(object): raise IOError(errno.EISDIR, destination, os.strerror(errno.ISDIR)) self.files[destination] = self.files[source] + self.written_files[destination] = self.files[source] def dirname(self, path): return self._split(path)[0] @@ -90,10 +99,10 @@ class MockFileSystem(object): if self.basename(path) in dirs_to_skip: return [] - if not path.endswith('/'): - path += '/' + if not path.endswith(self.sep): + path += self.sep - dir_substrings = ['/' + d + '/' for d in dirs_to_skip] + dir_substrings = [self.sep + d + self.sep for d in dirs_to_skip] for filename in self.files: if not filename.startswith(path): continue @@ -117,7 +126,7 @@ class MockFileSystem(object): return [f for f in self.files if f == path] def isabs(self, path): - return path.startswith('/') + return path.startswith(self.sep) def isfile(self, path): return path in self.files and self.files[path] is not None @@ -125,8 +134,8 @@ class MockFileSystem(object): def isdir(self, path): if path in self.files: return False - if not path.endswith('/'): - path += '/' + if not path.endswith(self.sep): + path += self.sep # We need to use a copy of the keys here in order to avoid switching # to a different thread and potentially modifying the dict in @@ -135,22 +144,24 @@ class MockFileSystem(object): return any(f.startswith(path) for f in files) def join(self, *comps): - return re.sub(re.escape(os.path.sep), '/', os.path.join(*comps)) + # FIXME: might want tests for this and/or a better comment about how + # it works. + return re.sub(re.escape(os.path.sep), self.sep, os.path.join(*comps)) def listdir(self, path): if not self.isdir(path): raise OSError("%s is not a directory" % path) - if not path.endswith('/'): - path += '/' + if not path.endswith(self.sep): + path += self.sep dirs = [] files = [] for f in self.files: if self.exists(f) and f.startswith(path): remaining = f[len(path):] - if '/' in remaining: - dir = remaining[:remaining.index('/')] + if self.sep in remaining: + dir = remaining[:remaining.index(self.sep)] if not dir in dirs: dirs.append(dir) else: @@ -164,7 +175,7 @@ class MockFileSystem(object): def _mktemp(self, suffix='', prefix='tmp', dir=None, **kwargs): if dir is None: - dir = '/__im_tmp' + dir = self.sep + '__im_tmp' curno = self.current_tmpno self.current_tmpno += 1 return self.join(dir, "%s_%u_%s" % (prefix, curno, suffix)) @@ -196,24 +207,26 @@ class MockFileSystem(object): # FIXME: Implement such that subsequent calls to isdir() work? pass - def move(self, src, dst): - if self.files[src] is None: - self._raise_not_found(src) - self.files[dst] = self.files[src] - self.files[src] = None + def move(self, source, destination): + if self.files[source] is None: + self._raise_not_found(source) + self.files[destination] = self.files[source] + self.written_files[destination] = self.files[destination] + self.files[source] = None + self.written_files[source] = None def normpath(self, path): return path - def open_binary_tempfile(self, suffix): + def open_binary_tempfile(self, suffix=''): path = self._mktemp(suffix) - return WritableFileObject(self, path), path + return (WritableFileObject(self, path), path) def open_text_file_for_writing(self, path, append=False): return WritableFileObject(self, path, append) def read_text_file(self, path): - return self.read_binary_file(path) + return self.read_binary_file(path).decode('utf-8') def read_binary_file(self, path): # Intentionally raises KeyError if we don't recognize the path. @@ -221,14 +234,18 @@ class MockFileSystem(object): self._raise_not_found(path) return self.files[path] + def relpath(self, path, start='.'): + return ospath.relpath(path, start, self.abspath, self.sep) + def remove(self, path): if self.files[path] is None: self._raise_not_found(path) self.files[path] = None + self.written_files[path] = None def rmtree(self, path): - if not path.endswith('/'): - path += '/' + if not path.endswith(self.sep): + path += self.sep for f in self.files: if f.startswith(path): @@ -241,7 +258,7 @@ class MockFileSystem(object): return (path[0:idx], path[idx:]) def write_text_file(self, path, contents): - return self.write_binary_file(path, contents) + return self.write_binary_file(path, contents.encode('utf-8')) def write_binary_file(self, path, contents): self.files[path] = contents diff --git a/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py b/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py index 267ca13..8455d72 100644 --- a/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py +++ b/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py @@ -167,6 +167,19 @@ class FileSystemTest(unittest.TestCase): self.assertTrue(fs.remove('filename', remove_with_exception)) self.assertEquals(-1, FileSystemTest._remove_failures) + def test_sep(self): + fs = FileSystem() + + self.assertEquals(fs.sep, os.sep) + self.assertEquals(fs.join("foo", "bar"), + os.path.join("foo", "bar")) + + def test_sep__is_readonly(self): + def assign_sep(): + fs.sep = ' ' + fs = FileSystem() + self.assertRaises(AttributeError, assign_sep) + if __name__ == '__main__': unittest.main() diff --git a/Tools/Scripts/webkitpy/common/system/logutils.py b/Tools/Scripts/webkitpy/common/system/logutils.py index cd4e60f..eef4636 100644 --- a/Tools/Scripts/webkitpy/common/system/logutils.py +++ b/Tools/Scripts/webkitpy/common/system/logutils.py @@ -83,7 +83,7 @@ def get_logger(path): Sample usage: - import webkitpy.common.system.logutils as logutils + from webkitpy.common.system import logutils _log = logutils.get_logger(__file__) diff --git a/Tools/Scripts/webkitpy/common/system/logutils_unittest.py b/Tools/Scripts/webkitpy/common/system/logutils_unittest.py index b77c284..f1b494d 100644 --- a/Tools/Scripts/webkitpy/common/system/logutils_unittest.py +++ b/Tools/Scripts/webkitpy/common/system/logutils_unittest.py @@ -28,7 +28,7 @@ import unittest from webkitpy.common.system.logtesting import LogTesting from webkitpy.common.system.logtesting import TestLogStream -import webkitpy.common.system.logutils as logutils +from webkitpy.common.system import logutils class GetLoggerTest(unittest.TestCase): diff --git a/Tools/Scripts/webkitpy/common/system/ospath.py b/Tools/Scripts/webkitpy/common/system/ospath.py index aed7a3d..2504645 100644 --- a/Tools/Scripts/webkitpy/common/system/ospath.py +++ b/Tools/Scripts/webkitpy/common/system/ospath.py @@ -32,7 +32,7 @@ import os # # 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): +def relpath(path, start_path, os_path_abspath=None, sep=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. @@ -44,10 +44,12 @@ def relpath(path, start_path, os_path_abspath=None): 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. + sep: Path separator. Defaults to os.path.sep """ if os_path_abspath is None: os_path_abspath = os.path.abspath + sep = sep or os.sep # Since os_path_abspath() calls os.path.normpath()-- # @@ -67,11 +69,11 @@ def relpath(path, start_path, os_path_abspath=None): if not rel_path: # Then the paths are the same. pass - elif rel_path[0] == os.sep: + elif rel_path[0] == 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) + rel_path = rel_path.lstrip(sep) else: # We are in the case typified by the following example: # diff --git a/Tools/Scripts/webkitpy/common/system/stack_utils.py b/Tools/Scripts/webkitpy/common/system/stack_utils.py new file mode 100644 index 0000000..a343807 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/stack_utils.py @@ -0,0 +1,67 @@ +# Copyright (C) 2011 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. + +"""Simple routines for logging, obtaining thread stack information.""" + +import sys +import traceback + + +def log_thread_state(logger, name, thread_id, msg=''): + """Log information about the given thread state.""" + stack = _find_thread_stack(thread_id) + assert(stack is not None) + logger("") + logger("%s (tid %d) %s" % (name, thread_id, msg)) + _log_stack(logger, stack) + logger("") + + +def _find_thread_stack(thread_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 tid, stack in sys._current_frames().items(): + if tid == thread_id: + return stack + return None + + +def _log_stack(logger, stack): + """Log a stack trace to the logger callback.""" + for filename, lineno, name, line in traceback.extract_stack(stack): + logger('File: "%s", line %d, in %s' % (filename, lineno, name)) + if line: + logger(' %s' % line.strip()) + + +def log_traceback(logger, tb): + stack = traceback.extract_tb(tb) + for frame_str in traceback.format_list(stack): + for line in frame_str.split('\n'): + if line: + logger(" %s" % line) diff --git a/Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py b/Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py new file mode 100644 index 0000000..b21319f --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/stack_utils_unittest.py @@ -0,0 +1,76 @@ +# Copyright (C) 2011 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.common.system import outputcapture +from webkitpy.common.system import stack_utils + + +def current_thread_id(): + thread_id, _ = sys._current_frames().items()[0] + return thread_id + + +class Test(unittest.TestCase): + def test_find_thread_stack_found(self): + thread_id = current_thread_id() + found_stack = stack_utils._find_thread_stack(thread_id) + self.assertNotEqual(found_stack, None) + + def test_find_thread_stack_not_found(self): + found_stack = stack_utils._find_thread_stack(0) + self.assertEqual(found_stack, None) + + def test_log_thread_state(self): + msgs = [] + + def logger(msg): + msgs.append(msg) + + thread_id = current_thread_id() + stack_utils.log_thread_state(logger, "test-thread", thread_id, + "is tested") + self.assertTrue(msgs) + + def test_log_traceback(self): + msgs = [] + + def logger(msg): + msgs.append(msg) + + try: + raise ValueError + except: + stack_utils.log_traceback(logger, sys.exc_info()[2]) + self.assertTrue(msgs) + + +if __name__ == '__main__': + unittest.main() diff --git a/Tools/Scripts/webkitpy/common/system/urlfetcher.py b/Tools/Scripts/webkitpy/common/system/urlfetcher.py new file mode 100644 index 0000000..2d9e5ec --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/urlfetcher.py @@ -0,0 +1,55 @@ +# Copyright (C) 2011 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 module for fetching URLs.""" + +import urllib + + +class UrlFetcher(object): + """Class with restricted interface to fetch URLs (makes testing easier)""" + def __init__(self, filesystem): + self._filesystem = filesystem + + def fetch(self, url): + """Fetches the contents of the URL as a string.""" + file_object = urllib.urlopen(url) + content = file_object.read() + file_object.close() + return content + + def fetch_into_file(self, url): + """Fetches the contents of the URL into a temporary file and return the filename. + + This is the equivalent of urllib.retrieve() except that we don't return any headers. + """ + file_object, filename = self._filesystem.open_binary_tempfile('-fetched') + contents = self.fetch(url) + file_object.write(contents) + file_object.close() + return filename diff --git a/Tools/Scripts/webkitpy/common/system/urlfetcher_mock.py b/Tools/Scripts/webkitpy/common/system/urlfetcher_mock.py new file mode 100644 index 0000000..e8a7532 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/urlfetcher_mock.py @@ -0,0 +1,46 @@ +# Copyright (C) 2011 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. + + +def make_fetcher_cls(urls): + """UrlFetcher factory routine that simulates network access + using a dict of URLs -> contents.""" + class MockFetcher(object): + def __init__(self, filesystem): + self._filesystem = filesystem + + def fetch(self, url): + return urls[url] + + def fetch_into_file(self, url): + f, fn = self._filesystem.open_binary_tempfile('mockfetcher') + f.write(self.fetch(url)) + f.close() + return fn + + return MockFetcher diff --git a/Tools/Scripts/webkitpy/common/system/zip_mock.py b/Tools/Scripts/webkitpy/common/system/zip_mock.py new file mode 100644 index 0000000..dcfaba7 --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/zip_mock.py @@ -0,0 +1,55 @@ +# 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. + +from webkitpy.common.system.fileset import FileSetFileHandle +from webkitpy.common.system.filesystem_mock import MockFileSystem + + +class MockZip(object): + """A mock zip file that can have new files inserted into it.""" + def __init__(self, filesystem=None): + self._filesystem = filesystem or MockFileSystem() + self._files = {} + + def __str__(self): + return "MockZip" + + def insert(self, filename, content): + self._files[filename] = content + + def namelist(self): + return self._files.keys() + + def open(self, filename): + return FileSetFileHandle(self, filename) + + def read(self, filename): + return self._files[filename] + + def extract(self, filename, path): + full_path = self._filesystem.join(path, filename) + contents = self.open(filename).contents() + self._filesystem.write_text_file(full_path, contents) + + def delete(self, filename): + self._files[filename] = None diff --git a/Tools/Scripts/webkitpy/common/system/zipfileset.py b/Tools/Scripts/webkitpy/common/system/zipfileset.py index fa2b762..5cf3616 100644 --- a/Tools/Scripts/webkitpy/common/system/zipfileset.py +++ b/Tools/Scripts/webkitpy/common/system/zipfileset.py @@ -33,22 +33,28 @@ class ZipFileSet(object): """The set of files in a zip file that resides at a URL (local or remote)""" def __init__(self, zip_url, filesystem=None, zip_factory=None): self._zip_url = zip_url + self._temp_file = None self._zip_file = None self._filesystem = filesystem or FileSystem() self._zip_factory = zip_factory or self._retrieve_zip_file def _retrieve_zip_file(self, zip_url): temp_file = NetworkTransaction().run(lambda: urllib.urlretrieve(zip_url)[0]) - return zipfile.ZipFile(temp_file) + return (temp_file, zipfile.ZipFile(temp_file)) def _load(self): if self._zip_file is None: - self._zip_file = self._zip_factory(self._zip_url) + self._temp_file, self._zip_file = self._zip_factory(self._zip_url) def open(self, filename): self._load() return FileSetFileHandle(self, filename, self._filesystem) + def close(self): + if self._temp_file: + self._filesystem.remove(self._temp_file) + self._temp_file = None + def namelist(self): self._load() return self._zip_file.namelist() diff --git a/Tools/Scripts/webkitpy/common/system/zipfileset_mock.py b/Tools/Scripts/webkitpy/common/system/zipfileset_mock.py new file mode 100644 index 0000000..24ac8cb --- /dev/null +++ b/Tools/Scripts/webkitpy/common/system/zipfileset_mock.py @@ -0,0 +1,51 @@ +# Copyright (C) 2011 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. + + +def make_factory(ziphashes): + """ZipFileSet factory routine that looks up zipfiles in a dict; + each zipfile should also be a dict of member names -> contents.""" + class MockZipFileSet(object): + def __init__(self, url): + self._url = url + self._ziphash = ziphashes[url] + + def namelist(self): + return self._ziphash.keys() + + def read(self, member): + return self._ziphash[member] + + def close(self): + pass + + def maker(url): + # We return None because there's no tempfile to delete. + return (None, MockZipFileSet(url)) + + return maker diff --git a/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py b/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py index a9ba5ad..6801406 100644 --- a/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py +++ b/Tools/Scripts/webkitpy/common/system/zipfileset_unittest.py @@ -64,13 +64,17 @@ class ZipFileSetTest(unittest.TestCase): result = FakeZip(self._filesystem) result.add_file('some-file', 'contents') result.add_file('a/b/some-other-file', 'other contents') - return result + return (None, result) def test_open(self): file = self._zip.open('a/b/some-other-file') self.assertEquals('a/b/some-other-file', file.name()) self.assertEquals('other contents', file.contents()) + def test_close(self): + zipfileset = ZipFileSet('blah', self._filesystem, self.make_fake_zip) + zipfileset.close() + def test_read(self): self.assertEquals('contents', self._zip.read('some-file')) |
